mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
feat: allow templates to specify max_ttl or autostop_requirement (#10920)
This commit is contained in:
10
coderd/apidoc/docs.go
generated
10
coderd/apidoc/docs.go
generated
@ -8031,7 +8031,7 @@ const docTemplate = `{
|
||||
]
|
||||
},
|
||||
"autostop_requirement": {
|
||||
"description": "AutostopRequirement allows optionally specifying the autostop requirement\nfor workspaces created from this template. This is an enterprise feature.",
|
||||
"description": "AutostopRequirement allows optionally specifying the autostop requirement\nfor workspaces created from this template. This is an enterprise feature.\nOnly one of MaxTTLMillis or AutostopRequirement can be specified.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.TemplateAutostopRequirement"
|
||||
@ -8071,7 +8071,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured",
|
||||
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured\nOnly one of MaxTTLMillis or AutostopRequirement can be specified.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
@ -8805,7 +8805,6 @@ const docTemplate = `{
|
||||
"workspace_actions",
|
||||
"tailnet_pg_coordinator",
|
||||
"single_tailnet",
|
||||
"template_autostop_requirement",
|
||||
"deployment_health_page",
|
||||
"template_update_policies"
|
||||
],
|
||||
@ -8814,7 +8813,6 @@ const docTemplate = `{
|
||||
"ExperimentWorkspaceActions",
|
||||
"ExperimentTailnetPGCoordinator",
|
||||
"ExperimentSingleTailnet",
|
||||
"ExperimentTemplateAutostopRequirement",
|
||||
"ExperimentDeploymentHealthPage",
|
||||
"ExperimentTemplateUpdatePolicies"
|
||||
]
|
||||
@ -10378,6 +10376,10 @@ const docTemplate = `{
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"use_max_ttl": {
|
||||
"description": "UseMaxTTL picks whether to use the deprecated max TTL for the template or\nthe new autostop requirement.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
10
coderd/apidoc/swagger.json
generated
10
coderd/apidoc/swagger.json
generated
@ -7152,7 +7152,7 @@
|
||||
]
|
||||
},
|
||||
"autostop_requirement": {
|
||||
"description": "AutostopRequirement allows optionally specifying the autostop requirement\nfor workspaces created from this template. This is an enterprise feature.",
|
||||
"description": "AutostopRequirement allows optionally specifying the autostop requirement\nfor workspaces created from this template. This is an enterprise feature.\nOnly one of MaxTTLMillis or AutostopRequirement can be specified.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.TemplateAutostopRequirement"
|
||||
@ -7192,7 +7192,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured",
|
||||
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured\nOnly one of MaxTTLMillis or AutostopRequirement can be specified.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
@ -7885,7 +7885,6 @@
|
||||
"workspace_actions",
|
||||
"tailnet_pg_coordinator",
|
||||
"single_tailnet",
|
||||
"template_autostop_requirement",
|
||||
"deployment_health_page",
|
||||
"template_update_policies"
|
||||
],
|
||||
@ -7894,7 +7893,6 @@
|
||||
"ExperimentWorkspaceActions",
|
||||
"ExperimentTailnetPGCoordinator",
|
||||
"ExperimentSingleTailnet",
|
||||
"ExperimentTemplateAutostopRequirement",
|
||||
"ExperimentDeploymentHealthPage",
|
||||
"ExperimentTemplateUpdatePolicies"
|
||||
]
|
||||
@ -9374,6 +9372,10 @@
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"use_max_ttl": {
|
||||
"description": "UseMaxTTL picks whether to use the deprecated max TTL for the template or\nthe new autostop requirement.",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -6158,6 +6158,7 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database
|
||||
tpl.AllowUserAutostop = arg.AllowUserAutostop
|
||||
tpl.UpdatedAt = dbtime.Now()
|
||||
tpl.DefaultTTL = arg.DefaultTTL
|
||||
tpl.UseMaxTtl = arg.UseMaxTtl
|
||||
tpl.MaxTTL = arg.MaxTTL
|
||||
tpl.AutostopRequirementDaysOfWeek = arg.AutostopRequirementDaysOfWeek
|
||||
tpl.AutostopRequirementWeeks = arg.AutostopRequirementWeeks
|
||||
|
4
coderd/database/dump.sql
generated
4
coderd/database/dump.sql
generated
@ -814,7 +814,8 @@ CREATE TABLE templates (
|
||||
autostop_requirement_weeks bigint DEFAULT 0 NOT NULL,
|
||||
autostart_block_days_of_week smallint DEFAULT 0 NOT NULL,
|
||||
require_active_version boolean DEFAULT false NOT NULL,
|
||||
deprecated text DEFAULT ''::text NOT NULL
|
||||
deprecated text DEFAULT ''::text NOT NULL,
|
||||
use_max_ttl boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.';
|
||||
@ -863,6 +864,7 @@ CREATE VIEW template_with_users AS
|
||||
templates.autostart_block_days_of_week,
|
||||
templates.require_active_version,
|
||||
templates.deprecated,
|
||||
templates.use_max_ttl,
|
||||
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,
|
||||
COALESCE(visible_users.username, ''::text) AS created_by_username
|
||||
FROM (public.templates
|
||||
|
@ -0,0 +1,19 @@
|
||||
DROP VIEW template_with_users;
|
||||
|
||||
ALTER TABLE templates DROP COLUMN use_max_ttl;
|
||||
|
||||
CREATE VIEW
|
||||
template_with_users
|
||||
AS
|
||||
SELECT
|
||||
templates.*,
|
||||
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
|
||||
coalesce(visible_users.username, '') AS created_by_username
|
||||
FROM
|
||||
templates
|
||||
LEFT JOIN
|
||||
visible_users
|
||||
ON
|
||||
templates.created_by = visible_users.id;
|
||||
|
||||
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
|
@ -0,0 +1,28 @@
|
||||
-- Add column with default true, so existing templates will function as usual
|
||||
ALTER TABLE templates ADD COLUMN use_max_ttl boolean NOT NULL DEFAULT true;
|
||||
|
||||
-- Find any templates with autostop_requirement_days_of_week set and set them to
|
||||
-- use_max_ttl = false
|
||||
UPDATE templates SET use_max_ttl = false WHERE autostop_requirement_days_of_week != 0;
|
||||
|
||||
-- Alter column to default false, because we want autostop_requirement to be the
|
||||
-- default from now on
|
||||
ALTER TABLE templates ALTER COLUMN use_max_ttl SET DEFAULT false;
|
||||
|
||||
DROP VIEW template_with_users;
|
||||
|
||||
CREATE VIEW
|
||||
template_with_users
|
||||
AS
|
||||
SELECT
|
||||
templates.*,
|
||||
coalesce(visible_users.avatar_url, '') AS created_by_avatar_url,
|
||||
coalesce(visible_users.username, '') AS created_by_username
|
||||
FROM
|
||||
templates
|
||||
LEFT JOIN
|
||||
visible_users
|
||||
ON
|
||||
templates.created_by = visible_users.id;
|
||||
|
||||
COMMENT ON VIEW template_with_users IS 'Joins in the username + avatar url of the created by user.';
|
@ -89,6 +89,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
|
||||
&i.AutostartBlockDaysOfWeek,
|
||||
&i.RequireActiveVersion,
|
||||
&i.Deprecated,
|
||||
&i.UseMaxTtl,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
); err != nil {
|
||||
|
@ -1973,6 +1973,7 @@ type Template struct {
|
||||
AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"`
|
||||
RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"`
|
||||
Deprecated string `db:"deprecated" json:"deprecated"`
|
||||
UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"`
|
||||
CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"`
|
||||
CreatedByUsername string `db:"created_by_username" json:"created_by_username"`
|
||||
}
|
||||
@ -2014,6 +2015,7 @@ type TemplateTable struct {
|
||||
RequireActiveVersion bool `db:"require_active_version" json:"require_active_version"`
|
||||
// If set to a non empty string, the template will no longer be able to be used. The message will be displayed to the user.
|
||||
Deprecated string `db:"deprecated" json:"deprecated"`
|
||||
UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"`
|
||||
}
|
||||
|
||||
// Joins in the username + avatar url of the created by user.
|
||||
|
@ -5354,7 +5354,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem
|
||||
|
||||
const getTemplateByID = `-- name: GetTemplateByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, created_by_avatar_url, created_by_username
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, created_by_avatar_url, created_by_username
|
||||
FROM
|
||||
template_with_users
|
||||
WHERE
|
||||
@ -5394,6 +5394,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
&i.AutostartBlockDaysOfWeek,
|
||||
&i.RequireActiveVersion,
|
||||
&i.Deprecated,
|
||||
&i.UseMaxTtl,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
)
|
||||
@ -5402,7 +5403,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
|
||||
const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, created_by_avatar_url, created_by_username
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, created_by_avatar_url, created_by_username
|
||||
FROM
|
||||
template_with_users AS templates
|
||||
WHERE
|
||||
@ -5450,6 +5451,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
&i.AutostartBlockDaysOfWeek,
|
||||
&i.RequireActiveVersion,
|
||||
&i.Deprecated,
|
||||
&i.UseMaxTtl,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
)
|
||||
@ -5457,7 +5459,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
}
|
||||
|
||||
const getTemplates = `-- name: GetTemplates :many
|
||||
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, created_by_avatar_url, created_by_username FROM template_with_users AS templates
|
||||
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, created_by_avatar_url, created_by_username FROM template_with_users AS templates
|
||||
ORDER BY (name, id) ASC
|
||||
`
|
||||
|
||||
@ -5498,6 +5500,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
&i.AutostartBlockDaysOfWeek,
|
||||
&i.RequireActiveVersion,
|
||||
&i.Deprecated,
|
||||
&i.UseMaxTtl,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
); err != nil {
|
||||
@ -5516,7 +5519,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
|
||||
const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, created_by_avatar_url, created_by_username
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, max_ttl, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, use_max_ttl, created_by_avatar_url, created_by_username
|
||||
FROM
|
||||
template_with_users AS templates
|
||||
WHERE
|
||||
@ -5607,6 +5610,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
|
||||
&i.AutostartBlockDaysOfWeek,
|
||||
&i.RequireActiveVersion,
|
||||
&i.Deprecated,
|
||||
&i.UseMaxTtl,
|
||||
&i.CreatedByAvatarURL,
|
||||
&i.CreatedByUsername,
|
||||
); err != nil {
|
||||
@ -5811,13 +5815,14 @@ SET
|
||||
allow_user_autostart = $3,
|
||||
allow_user_autostop = $4,
|
||||
default_ttl = $5,
|
||||
max_ttl = $6,
|
||||
autostop_requirement_days_of_week = $7,
|
||||
autostop_requirement_weeks = $8,
|
||||
autostart_block_days_of_week = $9,
|
||||
failure_ttl = $10,
|
||||
time_til_dormant = $11,
|
||||
time_til_dormant_autodelete = $12
|
||||
use_max_ttl = $6,
|
||||
max_ttl = $7,
|
||||
autostop_requirement_days_of_week = $8,
|
||||
autostop_requirement_weeks = $9,
|
||||
autostart_block_days_of_week = $10,
|
||||
failure_ttl = $11,
|
||||
time_til_dormant = $12,
|
||||
time_til_dormant_autodelete = $13
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
@ -5828,6 +5833,7 @@ type UpdateTemplateScheduleByIDParams struct {
|
||||
AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"`
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
DefaultTTL int64 `db:"default_ttl" json:"default_ttl"`
|
||||
UseMaxTtl bool `db:"use_max_ttl" json:"use_max_ttl"`
|
||||
MaxTTL int64 `db:"max_ttl" json:"max_ttl"`
|
||||
AutostopRequirementDaysOfWeek int16 `db:"autostop_requirement_days_of_week" json:"autostop_requirement_days_of_week"`
|
||||
AutostopRequirementWeeks int64 `db:"autostop_requirement_weeks" json:"autostop_requirement_weeks"`
|
||||
@ -5844,6 +5850,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
|
||||
arg.AllowUserAutostart,
|
||||
arg.AllowUserAutostop,
|
||||
arg.DefaultTTL,
|
||||
arg.UseMaxTtl,
|
||||
arg.MaxTTL,
|
||||
arg.AutostopRequirementDaysOfWeek,
|
||||
arg.AutostopRequirementWeeks,
|
||||
|
@ -128,13 +128,14 @@ SET
|
||||
allow_user_autostart = $3,
|
||||
allow_user_autostop = $4,
|
||||
default_ttl = $5,
|
||||
max_ttl = $6,
|
||||
autostop_requirement_days_of_week = $7,
|
||||
autostop_requirement_weeks = $8,
|
||||
autostart_block_days_of_week = $9,
|
||||
failure_ttl = $10,
|
||||
time_til_dormant = $11,
|
||||
time_til_dormant_autodelete = $12
|
||||
use_max_ttl = $6,
|
||||
max_ttl = $7,
|
||||
autostop_requirement_days_of_week = $8,
|
||||
autostop_requirement_weeks = $9,
|
||||
autostart_block_days_of_week = $10,
|
||||
failure_ttl = $11,
|
||||
time_til_dormant = $12,
|
||||
time_til_dormant_autodelete = $13
|
||||
WHERE
|
||||
id = $1
|
||||
;
|
||||
|
@ -1113,11 +1113,11 @@ func TestCompleteJob(t *testing.T) {
|
||||
var store schedule.TemplateScheduleStore = schedule.MockTemplateScheduleStore{
|
||||
GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) {
|
||||
return schedule.TemplateScheduleOptions{
|
||||
UserAutostartEnabled: false,
|
||||
UserAutostopEnabled: c.templateAllowAutostop,
|
||||
DefaultTTL: c.templateDefaultTTL,
|
||||
MaxTTL: c.templateMaxTTL,
|
||||
UseAutostopRequirement: false,
|
||||
UserAutostartEnabled: false,
|
||||
UserAutostopEnabled: c.templateAllowAutostop,
|
||||
DefaultTTL: c.templateDefaultTTL,
|
||||
MaxTTL: c.templateMaxTTL,
|
||||
UseMaxTTL: true,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
@ -1333,11 +1333,11 @@ func TestCompleteJob(t *testing.T) {
|
||||
var templateScheduleStore schedule.TemplateScheduleStore = schedule.MockTemplateScheduleStore{
|
||||
GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) {
|
||||
return schedule.TemplateScheduleOptions{
|
||||
UserAutostartEnabled: false,
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: 0,
|
||||
UseAutostopRequirement: true,
|
||||
AutostopRequirement: c.templateAutostopRequirement,
|
||||
UserAutostartEnabled: false,
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: 0,
|
||||
UseMaxTTL: false,
|
||||
AutostopRequirement: c.templateAutostopRequirement,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
@ -112,13 +112,12 @@ func CalculateAutostop(ctx context.Context, params CalculateAutostopParams) (Aut
|
||||
|
||||
// Use the old algorithm for calculating max_deadline if the instance isn't
|
||||
// configured or entitled to use the new feature flag yet.
|
||||
// TODO(@dean): remove this once the feature flag is enabled for all
|
||||
if !templateSchedule.UseAutostopRequirement && templateSchedule.MaxTTL > 0 {
|
||||
if templateSchedule.UseMaxTTL && templateSchedule.MaxTTL > 0 {
|
||||
autostop.MaxDeadline = now.Add(templateSchedule.MaxTTL)
|
||||
}
|
||||
|
||||
// TODO(@dean): remove extra conditional
|
||||
if templateSchedule.UseAutostopRequirement && templateSchedule.AutostopRequirement.DaysOfWeek != 0 {
|
||||
// Otherwise, use the autostop_requirement algorithm.
|
||||
if !templateSchedule.UseMaxTTL && templateSchedule.AutostopRequirement.DaysOfWeek != 0 {
|
||||
// The template has a autostop requirement, so determine the max deadline
|
||||
// of this workspace build.
|
||||
|
||||
@ -130,8 +129,8 @@ func CalculateAutostop(ctx context.Context, params CalculateAutostopParams) (Aut
|
||||
}
|
||||
|
||||
// If the schedule is nil, that means the deployment isn't entitled to
|
||||
// use quiet hours or the default schedule has not been set. In this
|
||||
// case, do not set a max deadline on the workspace.
|
||||
// use quiet hours. In this case, do not set a max deadline on the
|
||||
// workspace.
|
||||
if userQuietHoursSchedule.Schedule != nil {
|
||||
loc := userQuietHoursSchedule.Schedule.Location()
|
||||
now := now.In(loc)
|
||||
|
@ -415,12 +415,12 @@ func TestCalculateAutoStop(t *testing.T) {
|
||||
templateScheduleStore := schedule.MockTemplateScheduleStore{
|
||||
GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) {
|
||||
return schedule.TemplateScheduleOptions{
|
||||
UserAutostartEnabled: false,
|
||||
UserAutostopEnabled: c.templateAllowAutostop,
|
||||
DefaultTTL: c.templateDefaultTTL,
|
||||
MaxTTL: c.templateMaxTTL,
|
||||
UseAutostopRequirement: !c.useMaxTTL,
|
||||
AutostopRequirement: c.templateAutostopRequirement,
|
||||
UserAutostartEnabled: false,
|
||||
UserAutostopEnabled: c.templateAllowAutostop,
|
||||
DefaultTTL: c.templateDefaultTTL,
|
||||
MaxTTL: c.templateMaxTTL,
|
||||
UseMaxTTL: c.useMaxTTL,
|
||||
AutostopRequirement: c.templateAutostopRequirement,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
@ -117,14 +117,12 @@ type TemplateScheduleOptions struct {
|
||||
UserAutostartEnabled bool `json:"user_autostart_enabled"`
|
||||
UserAutostopEnabled bool `json:"user_autostop_enabled"`
|
||||
DefaultTTL time.Duration `json:"default_ttl"`
|
||||
// TODO(@dean): remove MaxTTL once autostop_requirement is matured and the
|
||||
// default
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
// UseAutostopRequirement dictates whether the autostop requirement should
|
||||
// be used instead of MaxTTL. This is governed by the feature flag and
|
||||
// licensing.
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
// UseMaxTTL dictates whether the max_ttl should be used instead of
|
||||
// autostop_requirement for this template. This is governed by the template
|
||||
// and licensing.
|
||||
// TODO(@dean): remove this when we remove max_tll
|
||||
UseAutostopRequirement bool
|
||||
UseMaxTTL bool
|
||||
// AutostopRequirement dictates when the workspace must be restarted. This
|
||||
// used to be handled by MaxTTL.
|
||||
AutostopRequirement TemplateAutostopRequirement `json:"autostop_requirement"`
|
||||
@ -185,8 +183,8 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
// Disregard the values in the database, since AutostopRequirement,
|
||||
// FailureTTL, TimeTilDormant, and TimeTilDormantAutoDelete are enterprise features.
|
||||
UseAutostopRequirement: false,
|
||||
MaxTTL: 0,
|
||||
UseMaxTTL: false,
|
||||
MaxTTL: 0,
|
||||
AutostartRequirement: TemplateAutostartRequirement{
|
||||
// Default to allowing all days for AGPL
|
||||
DaysOfWeek: 0b01111111,
|
||||
@ -220,6 +218,7 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp
|
||||
DefaultTTL: int64(opts.DefaultTTL),
|
||||
// Don't allow changing these settings, but keep the value in the DB (to
|
||||
// avoid clearing settings if the license has an issue).
|
||||
UseMaxTtl: tpl.UseMaxTtl,
|
||||
MaxTTL: tpl.MaxTTL,
|
||||
AutostopRequirementDaysOfWeek: tpl.AutostopRequirementDaysOfWeek,
|
||||
AutostopRequirementWeeks: tpl.AutostopRequirementWeeks,
|
||||
|
@ -223,8 +223,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
var (
|
||||
defaultTTL time.Duration
|
||||
// TODO(@dean): remove max_ttl once autostop_requirement is ready
|
||||
defaultTTL time.Duration
|
||||
maxTTL time.Duration
|
||||
autostopRequirementDaysOfWeek []string
|
||||
autostartRequirementDaysOfWeek []string
|
||||
@ -285,6 +284,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
if createTemplate.MaxTTLMillis != nil {
|
||||
maxTTL = time.Duration(*createTemplate.MaxTTLMillis) * time.Millisecond
|
||||
}
|
||||
if maxTTL != 0 && len(autostopRequirementDaysOfWeek) > 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "autostop_requirement.days_of_week", Detail: "Cannot be set if max_ttl_ms is set."})
|
||||
}
|
||||
if autostopRequirementWeeks < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "autostop_requirement.weeks", Detail: "Must be a positive integer."})
|
||||
}
|
||||
@ -364,6 +366,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
dbTemplate, err = (*api.TemplateScheduleStore.Load()).Set(ctx, tx, dbTemplate, schedule.TemplateScheduleOptions{
|
||||
UserAutostartEnabled: allowUserAutostart,
|
||||
UserAutostopEnabled: allowUserAutostop,
|
||||
UseMaxTTL: maxTTL > 0,
|
||||
DefaultTTL: defaultTTL,
|
||||
MaxTTL: maxTTL,
|
||||
// Some of these values are enterprise-only, but the
|
||||
@ -568,6 +571,10 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
if req.MaxTTLMillis != 0 && req.DefaultTTLMillis > req.MaxTTLMillis {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be less than or equal to max_ttl_ms if max_ttl_ms is set."})
|
||||
}
|
||||
if req.MaxTTLMillis != 0 && req.AutostopRequirement != nil && len(req.AutostopRequirement.DaysOfWeek) > 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "autostop_requirement.days_of_week", Detail: "Cannot be set if max_ttl_ms is set."})
|
||||
}
|
||||
useMaxTTL := req.MaxTTLMillis > 0
|
||||
if req.AutostopRequirement == nil {
|
||||
req.AutostopRequirement = &codersdk.TemplateAutostopRequirement{
|
||||
DaysOfWeek: codersdk.BitmapToWeekdays(scheduleOpts.AutostopRequirement.DaysOfWeek),
|
||||
@ -641,6 +648,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
req.AllowUserAutostop == template.AllowUserAutostop &&
|
||||
req.AllowUserCancelWorkspaceJobs == template.AllowUserCancelWorkspaceJobs &&
|
||||
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() &&
|
||||
useMaxTTL == scheduleOpts.UseMaxTTL &&
|
||||
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() &&
|
||||
autostopRequirementDaysOfWeekParsed == scheduleOpts.AutostopRequirement.DaysOfWeek &&
|
||||
autostartRequirementDaysOfWeekParsed == scheduleOpts.AutostartRequirement.DaysOfWeek &&
|
||||
@ -695,6 +703,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
timeTilDormantAutoDelete := time.Duration(req.TimeTilDormantAutoDeleteMillis) * time.Millisecond
|
||||
|
||||
if defaultTTL != time.Duration(template.DefaultTTL) ||
|
||||
useMaxTTL != scheduleOpts.UseMaxTTL ||
|
||||
maxTTL != time.Duration(template.MaxTTL) ||
|
||||
autostopRequirementDaysOfWeekParsed != scheduleOpts.AutostopRequirement.DaysOfWeek ||
|
||||
autostartRequirementDaysOfWeekParsed != scheduleOpts.AutostartRequirement.DaysOfWeek ||
|
||||
@ -711,6 +720,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
UserAutostartEnabled: req.AllowUserAutostart,
|
||||
UserAutostopEnabled: req.AllowUserAutostop,
|
||||
DefaultTTL: defaultTTL,
|
||||
UseMaxTTL: useMaxTTL,
|
||||
MaxTTL: maxTTL,
|
||||
AutostopRequirement: schedule.TemplateAutostopRequirement{
|
||||
DaysOfWeek: autostopRequirementDaysOfWeekParsed,
|
||||
@ -859,6 +869,7 @@ func (api *API) convertTemplate(
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
|
||||
UseMaxTTL: template.UseMaxTtl,
|
||||
MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(),
|
||||
CreatedByID: template.CreatedBy,
|
||||
CreatedByName: template.CreatedByUsername,
|
||||
|
@ -268,6 +268,7 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
AllowUserAutostart: options.UserAutostartEnabled,
|
||||
AllowUserAutostop: options.UserAutostopEnabled,
|
||||
DefaultTTL: int64(options.DefaultTTL),
|
||||
UseMaxTtl: options.UseMaxTTL,
|
||||
MaxTTL: int64(options.MaxTTL),
|
||||
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
|
||||
@ -296,6 +297,7 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, got.UseMaxTTL) // default
|
||||
require.EqualValues(t, 1, atomic.LoadInt64(&setCalled))
|
||||
require.Empty(t, got.AutostopRequirement.DaysOfWeek)
|
||||
require.EqualValues(t, 1, got.AutostopRequirement.Weeks)
|
||||
@ -318,6 +320,7 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
AllowUserAutostart: options.UserAutostartEnabled,
|
||||
AllowUserAutostop: options.UserAutostopEnabled,
|
||||
DefaultTTL: int64(options.DefaultTTL),
|
||||
UseMaxTtl: options.UseMaxTTL,
|
||||
MaxTTL: int64(options.MaxTTL),
|
||||
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
|
||||
@ -351,11 +354,13 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, 1, atomic.LoadInt64(&setCalled))
|
||||
require.False(t, got.UseMaxTTL)
|
||||
require.Equal(t, []string{"friday", "saturday"}, got.AutostopRequirement.DaysOfWeek)
|
||||
require.EqualValues(t, 2, got.AutostopRequirement.Weeks)
|
||||
|
||||
got, err = client.Template(ctx, got.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, got.UseMaxTTL)
|
||||
require.Equal(t, []string{"friday", "saturday"}, got.AutostopRequirement.DaysOfWeek)
|
||||
require.EqualValues(t, 2, got.AutostopRequirement.Weeks)
|
||||
})
|
||||
@ -380,10 +385,36 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// ignored and use AGPL defaults
|
||||
require.False(t, got.UseMaxTTL)
|
||||
require.Empty(t, got.AutostopRequirement.DaysOfWeek)
|
||||
require.EqualValues(t, 1, got.AutostopRequirement.Weeks)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("BothMaxTTLAndAutostopRequirement", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Fake template schedule store is unneeded for this test since the
|
||||
// route fails before it is called.
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{
|
||||
Name: "testing",
|
||||
VersionID: version.ID,
|
||||
MaxTTLMillis: ptr.Ref(24 * time.Hour.Milliseconds()),
|
||||
AutostopRequirement: &codersdk.TemplateAutostopRequirement{
|
||||
DaysOfWeek: []string{"friday", "saturday"},
|
||||
Weeks: 2,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "max_ttl_ms")
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplatesByOrganization(t *testing.T) {
|
||||
@ -677,6 +708,7 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
AllowUserAutostop: options.UserAutostopEnabled,
|
||||
DefaultTTL: int64(options.DefaultTTL),
|
||||
MaxTTL: int64(options.MaxTTL),
|
||||
UseMaxTtl: options.UseMaxTTL,
|
||||
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
|
||||
FailureTTL: int64(options.FailureTTL),
|
||||
@ -1073,6 +1105,7 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
AllowUserAutostart: options.UserAutostartEnabled,
|
||||
AllowUserAutostop: options.UserAutostopEnabled,
|
||||
DefaultTTL: int64(options.DefaultTTL),
|
||||
UseMaxTtl: options.UseMaxTTL,
|
||||
MaxTTL: int64(options.MaxTTL),
|
||||
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
|
||||
@ -1144,6 +1177,7 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
AllowUserAutostart: options.UserAutostartEnabled,
|
||||
AllowUserAutostop: options.UserAutostopEnabled,
|
||||
DefaultTTL: int64(options.DefaultTTL),
|
||||
UseMaxTtl: options.UseMaxTTL,
|
||||
MaxTTL: int64(options.MaxTTL),
|
||||
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
|
||||
@ -1238,6 +1272,38 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
require.False(t, template.Deprecated)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("BothMaxTTLAndAutostopRequirement", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Fake template schedule store is unneeded for this test since the
|
||||
// route fails before it is called.
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
DefaultTTLMillis: time.Hour.Milliseconds(),
|
||||
MaxTTLMillis: time.Hour.Milliseconds(),
|
||||
AutostopRequirement: &codersdk.TemplateAutostopRequirement{
|
||||
DaysOfWeek: []string{"monday"},
|
||||
Weeks: 2,
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "max_ttl_ms")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteTemplate(t *testing.T) {
|
||||
|
@ -431,7 +431,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
maxTTL := templateSchedule.MaxTTL
|
||||
if templateSchedule.UseAutostopRequirement {
|
||||
if !templateSchedule.UseMaxTTL {
|
||||
// If we're using autostop requirements, there isn't a max TTL.
|
||||
maxTTL = 0
|
||||
}
|
||||
@ -787,7 +787,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
maxTTL := templateSchedule.MaxTTL
|
||||
if templateSchedule.UseAutostopRequirement {
|
||||
if !templateSchedule.UseMaxTTL {
|
||||
// If we're using autostop requirements, there isn't a max TTL.
|
||||
maxTTL = 0
|
||||
}
|
||||
|
Reference in New Issue
Block a user