mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add inactivity cleanup and failure cleanup configuration fields to Template Schedule Form (#7402)
* added workspace actions entitlement * added workspace actions experiment * added new route for template enterprise meta * removing new route; repurposing old * add new fields to get endpoints * removed workspace actions experiment * added logic to enterprise template store * added new form fields * feature flagged new fields * fix validation * fixed submit btn * fix tests * changed ttl defaults * added FE tests * added BE tests * fixed lint * adjusted comment language * fixing unstaged changes check * fix test * Update coderd/database/migrations/000122_add_template_cleanup_ttls.down.sql Co-authored-by: Dean Sheather <dean@deansheather.com> * Update coderd/database/migrations/000122_add_template_cleanup_ttls.up.sql Co-authored-by: Dean Sheather <dean@deansheather.com> --------- Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
co-authored by
Dean Sheather
parent
3632ac8c01
commit
5ffa6dae50
Generated
+15
@@ -6719,10 +6719,18 @@ const docTemplate = `{
|
||||
"description": "DisplayName is the displayed name of the template.",
|
||||
"type": "string"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis allows optionally specifying the max lifetime before Coder\nstops all resources for failed workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.",
|
||||
"type": "string"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"description": "InactivityTTLMillis allows optionally specifying the max lifetime before Coder\ndeletes inactive workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "MaxTTLMillis allows optionally specifying the max lifetime for\nworkspaces created from this template.",
|
||||
"type": "integer"
|
||||
@@ -8698,6 +8706,10 @@ const docTemplate = `{
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis and InactivityTTLMillis are enterprise-only. Their\nvalues are used if your license is entitled to use the advanced\ntemplate scheduling feature.",
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8705,6 +8717,9 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "MaxTTLMillis is an enterprise feature. It's value is only used if your\nlicense is entitled to use the advanced template scheduling feature.",
|
||||
"type": "integer"
|
||||
|
||||
Generated
+15
@@ -5978,10 +5978,18 @@
|
||||
"description": "DisplayName is the displayed name of the template.",
|
||||
"type": "string"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis allows optionally specifying the max lifetime before Coder\nstops all resources for failed workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.",
|
||||
"type": "string"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"description": "InactivityTTLMillis allows optionally specifying the max lifetime before Coder\ndeletes inactive workspaces created from this template.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "MaxTTLMillis allows optionally specifying the max lifetime for\nworkspaces created from this template.",
|
||||
"type": "integer"
|
||||
@@ -7816,6 +7824,10 @@
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"failure_ttl_ms": {
|
||||
"description": "FailureTTLMillis and InactivityTTLMillis are enterprise-only. Their\nvalues are used if your license is entitled to use the advanced\ntemplate scheduling feature.",
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -7823,6 +7835,9 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"inactivity_ttl_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"max_ttl_ms": {
|
||||
"description": "MaxTTLMillis is an enterprise feature. It's value is only used if your\nlicense is entitled to use the advanced template scheduling feature.",
|
||||
"type": "integer"
|
||||
|
||||
@@ -1945,6 +1945,8 @@ func (q *fakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database
|
||||
tpl.UpdatedAt = database.Now()
|
||||
tpl.DefaultTTL = arg.DefaultTTL
|
||||
tpl.MaxTTL = arg.MaxTTL
|
||||
tpl.FailureTTL = arg.FailureTTL
|
||||
tpl.InactivityTTL = arg.InactivityTTL
|
||||
q.templates[idx] = tpl
|
||||
return tpl.DeepCopy(), nil
|
||||
}
|
||||
|
||||
Generated
+3
-1
@@ -478,7 +478,9 @@ CREATE TABLE templates (
|
||||
allow_user_cancel_workspace_jobs boolean DEFAULT true NOT NULL,
|
||||
max_ttl bigint DEFAULT '0'::bigint NOT NULL,
|
||||
allow_user_autostart boolean DEFAULT true NOT NULL,
|
||||
allow_user_autostop boolean DEFAULT true NOT NULL
|
||||
allow_user_autostop boolean DEFAULT true NOT NULL,
|
||||
failure_ttl bigint DEFAULT 0 NOT NULL,
|
||||
inactivity_ttl bigint DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
BEGIN;
|
||||
ALTER TABLE ONLY templates DROP COLUMN IF EXISTS failure_ttl;
|
||||
ALTER TABLE ONLY templates DROP COLUMN IF EXISTS inactivity_ttl;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,4 @@
|
||||
BEGIN;
|
||||
ALTER TABLE ONLY templates ADD COLUMN IF NOT EXISTS failure_ttl BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE ONLY templates ADD COLUMN IF NOT EXISTS inactivity_ttl BIGINT NOT NULL DEFAULT 0;
|
||||
COMMIT;
|
||||
@@ -80,6 +80,8 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1436,7 +1436,9 @@ type Template struct {
|
||||
// Allow users to specify an autostart schedule for workspaces (enterprise).
|
||||
AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"`
|
||||
// Allow users to specify custom autostop values for workspaces (enterprise).
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
InactivityTTL int64 `db:"inactivity_ttl" json:"inactivity_ttl"`
|
||||
}
|
||||
|
||||
type TemplateVersion struct {
|
||||
|
||||
@@ -3490,7 +3490,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
|
||||
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, inactivity_ttl
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@@ -3522,13 +3522,15 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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
|
||||
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, inactivity_ttl
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@@ -3568,12 +3570,14 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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 FROM 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, inactivity_ttl FROM templates
|
||||
ORDER BY (name, id) ASC
|
||||
`
|
||||
|
||||
@@ -3606,6 +3610,8 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3622,7 +3628,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
|
||||
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, inactivity_ttl
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@@ -3692,6 +3698,8 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3725,7 +3733,7 @@ INSERT INTO
|
||||
allow_user_cancel_workspace_jobs
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING 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
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING 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, inactivity_ttl
|
||||
`
|
||||
|
||||
type InsertTemplateParams struct {
|
||||
@@ -3783,6 +3791,8 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3796,7 +3806,7 @@ SET
|
||||
WHERE
|
||||
id = $3
|
||||
RETURNING
|
||||
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
|
||||
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, inactivity_ttl
|
||||
`
|
||||
|
||||
type UpdateTemplateACLByIDParams struct {
|
||||
@@ -3828,6 +3838,8 @@ func (q *sqlQuerier) UpdateTemplateACLByID(ctx context.Context, arg UpdateTempla
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3887,7 +3899,7 @@ SET
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
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
|
||||
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, inactivity_ttl
|
||||
`
|
||||
|
||||
type UpdateTemplateMetaByIDParams struct {
|
||||
@@ -3931,6 +3943,8 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3943,11 +3957,13 @@ SET
|
||||
allow_user_autostart = $3,
|
||||
allow_user_autostop = $4,
|
||||
default_ttl = $5,
|
||||
max_ttl = $6
|
||||
max_ttl = $6,
|
||||
failure_ttl = $7,
|
||||
inactivity_ttl = $8
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
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
|
||||
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, inactivity_ttl
|
||||
`
|
||||
|
||||
type UpdateTemplateScheduleByIDParams struct {
|
||||
@@ -3957,6 +3973,8 @@ type UpdateTemplateScheduleByIDParams struct {
|
||||
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
|
||||
DefaultTTL int64 `db:"default_ttl" json:"default_ttl"`
|
||||
MaxTTL int64 `db:"max_ttl" json:"max_ttl"`
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
InactivityTTL int64 `db:"inactivity_ttl" json:"inactivity_ttl"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) (Template, error) {
|
||||
@@ -3967,6 +3985,8 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
|
||||
arg.AllowUserAutostop,
|
||||
arg.DefaultTTL,
|
||||
arg.MaxTTL,
|
||||
arg.FailureTTL,
|
||||
arg.InactivityTTL,
|
||||
)
|
||||
var i Template
|
||||
err := row.Scan(
|
||||
@@ -3989,6 +4009,8 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
|
||||
&i.MaxTTL,
|
||||
&i.AllowUserAutostart,
|
||||
&i.AllowUserAutostop,
|
||||
&i.FailureTTL,
|
||||
&i.InactivityTTL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -118,7 +118,9 @@ SET
|
||||
allow_user_autostart = $3,
|
||||
allow_user_autostop = $4,
|
||||
default_ttl = $5,
|
||||
max_ttl = $6
|
||||
max_ttl = $6,
|
||||
failure_ttl = $7,
|
||||
inactivity_ttl = $8
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
|
||||
@@ -51,6 +51,8 @@ overrides:
|
||||
template_max_ttl: TemplateMaxTTL
|
||||
motd_file: MOTDFile
|
||||
uuid: UUID
|
||||
failure_ttl: FailureTTL
|
||||
inactivity_ttl: InactivityTTL
|
||||
|
||||
sql:
|
||||
- schema: "./dump.sql"
|
||||
|
||||
@@ -18,6 +18,10 @@ type TemplateScheduleOptions struct {
|
||||
//
|
||||
// If set, users cannot disable automatic workspace shutdown.
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
// If FailureTTL is set, all failed workspaces will be stopped automatically after this time has elapsed.
|
||||
FailureTTL time.Duration `json:"failure_ttl"`
|
||||
// If InactivityTTL is set, all inactive workspaces will be deleted automatically after this time has elapsed.
|
||||
InactivityTTL time.Duration `json:"inactivity_ttl"`
|
||||
}
|
||||
|
||||
// TemplateScheduleStore provides an interface for retrieving template
|
||||
@@ -47,9 +51,11 @@ func (*agplTemplateScheduleStore) GetTemplateScheduleOptions(ctx context.Context
|
||||
UserAutostartEnabled: true,
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
// Disregard the value in the database, since MaxTTL is an enterprise
|
||||
// feature.
|
||||
MaxTTL: 0,
|
||||
// Disregard the values in the database, since MaxTTL, FailureTTL, and InactivityTTL are enterprise
|
||||
// features.
|
||||
MaxTTL: 0,
|
||||
FailureTTL: 0,
|
||||
InactivityTTL: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -68,5 +74,7 @@ func (*agplTemplateScheduleStore) SetTemplateScheduleOptions(ctx context.Context
|
||||
AllowUserAutostart: tpl.AllowUserAutostart,
|
||||
AllowUserAutostop: tpl.AllowUserAutostop,
|
||||
MaxTTL: tpl.MaxTTL,
|
||||
FailureTTL: tpl.FailureTTL,
|
||||
InactivityTTL: tpl.InactivityTTL,
|
||||
})
|
||||
}
|
||||
|
||||
+22
-11
@@ -476,6 +476,12 @@ 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.FailureTTLMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "failure_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if req.InactivityTTLMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "inactivity_ttl_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
|
||||
if len(validErrs) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
@@ -495,18 +501,14 @@ 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() &&
|
||||
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() {
|
||||
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() &&
|
||||
req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() &&
|
||||
req.InactivityTTLMillis == time.Duration(template.InactivityTTL).Milliseconds() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update template metadata -- empty fields are not overwritten,
|
||||
// except for display_name, description, icon, and default_ttl.
|
||||
// These exceptions are required to clear content of these fields with UI.
|
||||
// Users should not be able to clear the template name in the UI
|
||||
name := req.Name
|
||||
displayName := req.DisplayName
|
||||
desc := req.Description
|
||||
icon := req.Icon
|
||||
|
||||
if name == "" {
|
||||
name = template.Name
|
||||
}
|
||||
@@ -516,9 +518,9 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
ID: template.ID,
|
||||
UpdatedAt: database.Now(),
|
||||
Name: name,
|
||||
DisplayName: displayName,
|
||||
Description: desc,
|
||||
Icon: icon,
|
||||
DisplayName: req.DisplayName,
|
||||
Description: req.Description,
|
||||
Icon: req.Icon,
|
||||
AllowUserCancelWorkspaceJobs: req.AllowUserCancelWorkspaceJobs,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -527,8 +529,13 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
|
||||
maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond
|
||||
failureTTL := time.Duration(req.FailureTTLMillis) * time.Millisecond
|
||||
inactivityTTL := time.Duration(req.InactivityTTLMillis) * time.Millisecond
|
||||
|
||||
if defaultTTL != time.Duration(template.DefaultTTL) ||
|
||||
maxTTL != time.Duration(template.MaxTTL) ||
|
||||
failureTTL != time.Duration(template.FailureTTL) ||
|
||||
inactivityTTL != time.Duration(template.InactivityTTL) ||
|
||||
req.AllowUserAutostart != template.AllowUserAutostart ||
|
||||
req.AllowUserAutostop != template.AllowUserAutostop {
|
||||
updated, err = (*api.TemplateScheduleStore.Load()).SetTemplateScheduleOptions(ctx, tx, updated, schedule.TemplateScheduleOptions{
|
||||
@@ -539,6 +546,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
UserAutostopEnabled: req.AllowUserAutostop,
|
||||
DefaultTTL: defaultTTL,
|
||||
MaxTTL: maxTTL,
|
||||
FailureTTL: failureTTL,
|
||||
InactivityTTL: inactivityTTL,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("set template schedule options: %w", err)
|
||||
@@ -678,5 +687,7 @@ func (api *API) convertTemplate(
|
||||
AllowUserAutostart: template.AllowUserAutostart,
|
||||
AllowUserAutostop: template.AllowUserAutostop,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: time.Duration(template.FailureTTL).Milliseconds(),
|
||||
InactivityTTLMillis: time.Duration(template.InactivityTTL).Milliseconds(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,6 +604,90 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("CleanupTTLs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
failureTTL = 7 * 24 * time.Hour
|
||||
inactivityTTL = 180 * 24 * time.Hour
|
||||
)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var setCalled int64
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
TemplateScheduleStore: schedule.MockTemplateScheduleStore{
|
||||
SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) {
|
||||
if atomic.AddInt64(&setCalled, 1) == 2 {
|
||||
require.Equal(t, failureTTL, options.FailureTTL)
|
||||
require.Equal(t, inactivityTTL, options.InactivityTTL)
|
||||
}
|
||||
template.FailureTTL = int64(options.FailureTTL)
|
||||
template.InactivityTTL = int64(options.InactivityTTL)
|
||||
return template, nil
|
||||
},
|
||||
},
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.InactivityTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
got, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: 0,
|
||||
MaxTTLMillis: 0,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, 2, atomic.LoadInt64(&setCalled))
|
||||
require.Equal(t, failureTTL.Milliseconds(), got.FailureTTLMillis)
|
||||
require.Equal(t, inactivityTTL.Milliseconds(), got.InactivityTTLMillis)
|
||||
})
|
||||
|
||||
t.Run("IgnoredUnlicensed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.FailureTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
ctr.InactivityTTLMillis = ptr.Ref(0 * time.Hour.Milliseconds())
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
got, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: template.DefaultTTLMillis,
|
||||
MaxTTLMillis: template.MaxTTLMillis,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, got.FailureTTLMillis)
|
||||
require.Zero(t, got.InactivityTTLMillis)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("AllowUserScheduling", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user