mirror of
https://github.com/coder/coder.git
synced 2026-10-25 09:42:55 +08:00
feat: plumb time_til_autostop_notify template field (#26439)
This commit is contained in:
+17
-6
@@ -23,6 +23,7 @@ func (r *RootCmd) templateEdit() *serpent.Command {
|
||||
icon string
|
||||
defaultTTL time.Duration
|
||||
activityBump time.Duration
|
||||
timeTilAutostopNotify time.Duration
|
||||
autostopRequirementDaysOfWeek []string
|
||||
autostopRequirementWeeks int64
|
||||
autostartRequirementDaysOfWeek []string
|
||||
@@ -113,6 +114,10 @@ func (r *RootCmd) templateEdit() *serpent.Command {
|
||||
activityBump = time.Duration(template.ActivityBumpMillis) * time.Millisecond
|
||||
}
|
||||
|
||||
if !userSetOption(inv, "autostop-reminder") {
|
||||
timeTilAutostopNotify = time.Duration(template.TimeTilAutostopNotifyMillis) * time.Millisecond
|
||||
}
|
||||
|
||||
if !userSetOption(inv, "allow-user-autostop") {
|
||||
allowUserAutostop = template.AllowUserAutostop
|
||||
}
|
||||
@@ -174,12 +179,13 @@ func (r *RootCmd) templateEdit() *serpent.Command {
|
||||
}
|
||||
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
Name: &name,
|
||||
DisplayName: &displayName,
|
||||
Description: &description,
|
||||
Icon: &icon,
|
||||
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
|
||||
ActivityBumpMillis: ptr.Ref(activityBump.Milliseconds()),
|
||||
Name: &name,
|
||||
DisplayName: &displayName,
|
||||
Description: &description,
|
||||
Icon: &icon,
|
||||
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
|
||||
ActivityBumpMillis: ptr.Ref(activityBump.Milliseconds()),
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(timeTilAutostopNotify.Milliseconds()),
|
||||
AutostopRequirement: &codersdk.TemplateAutostopRequirement{
|
||||
DaysOfWeek: autostopRequirementDaysOfWeek,
|
||||
Weeks: autostopRequirementWeeks,
|
||||
@@ -248,6 +254,11 @@ func (r *RootCmd) templateEdit() *serpent.Command {
|
||||
Description: "Edit the template activity bump - workspaces created from this template will have their shutdown time bumped by this value when activity is detected. Maps to \"Activity bump\" in the UI.",
|
||||
Value: serpent.DurationOf(&activityBump),
|
||||
},
|
||||
{
|
||||
Flag: "autostop-reminder",
|
||||
Description: "Edit how long before the autostop deadline a reminder notification is sent for workspaces created from this template, in Go duration format (e.g. 1h, 30m). Set to 0 to disable.",
|
||||
Value: serpent.DurationOf(&timeTilAutostopNotify),
|
||||
},
|
||||
{
|
||||
Flag: "autostart-requirement-weekdays",
|
||||
Description: "Edit the template autostart requirement weekdays - workspaces created from this template can only autostart on the given weekdays. To unset this value for the template (and allow autostart on all days), pass 'all'.",
|
||||
|
||||
@@ -44,6 +44,7 @@ func TestTemplateEdit(t *testing.T) {
|
||||
desc := "lorem ipsum dolor sit amet et cetera"
|
||||
icon := "/icon/new-icon.png"
|
||||
defaultTTL := 12 * time.Hour
|
||||
timeTilAutostopNotify := 5 * time.Minute
|
||||
allowUserCancelWorkspaceJobs := false
|
||||
|
||||
cmdArgs := []string{
|
||||
@@ -55,6 +56,7 @@ func TestTemplateEdit(t *testing.T) {
|
||||
"--description", desc,
|
||||
"--icon", icon,
|
||||
"--default-ttl", defaultTTL.String(),
|
||||
"--autostop-reminder", timeTilAutostopNotify.String(),
|
||||
"--allow-user-cancel-workspace-jobs=" + strconv.FormatBool(allowUserCancelWorkspaceJobs),
|
||||
}
|
||||
inv, root := clitest.New(t, cmdArgs...)
|
||||
@@ -73,6 +75,7 @@ func TestTemplateEdit(t *testing.T) {
|
||||
assert.Equal(t, desc, updated.Description)
|
||||
assert.Equal(t, icon, updated.Icon)
|
||||
assert.Equal(t, defaultTTL.Milliseconds(), updated.DefaultTTLMillis)
|
||||
assert.Equal(t, timeTilAutostopNotify.Milliseconds(), updated.TimeTilAutostopNotifyMillis)
|
||||
assert.Equal(t, allowUserCancelWorkspaceJobs, updated.AllowUserCancelWorkspaceJobs)
|
||||
})
|
||||
t.Run("FirstEmptyThenNotModified", func(t *testing.T) {
|
||||
|
||||
@@ -31,6 +31,11 @@ OPTIONS:
|
||||
this value for the template (and allow autostart on all days), pass
|
||||
'all'.
|
||||
|
||||
--autostop-reminder duration
|
||||
Edit how long before the autostop deadline a reminder notification is
|
||||
sent for workspaces created from this template, in Go duration format
|
||||
(e.g. 1h, 30m). Set to 0 to disable.
|
||||
|
||||
--autostop-requirement-weekdays [monday|tuesday|wednesday|thursday|friday|saturday|sunday|none]
|
||||
Edit the template autostop requirement weekdays - workspaces created
|
||||
from this template must be restarted on the given weekdays. To unset
|
||||
|
||||
Generated
+12
@@ -18437,6 +18437,10 @@ const docTemplate = `{
|
||||
"description": "VersionID is an in-progress or completed job to use as an initial version\nof the template.\n\nThis is required on creation to enable a user-flow of validating a\ntemplate works. There is no reason the data-model cannot support empty\ntemplates, but it doesn't make sense for users.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"time_til_autostop_notify_ms": {
|
||||
"description": "TimeTilAutostopNotifyMillis allows optionally specifying the duration\nbefore the autostop deadline at which a reminder notification is sent for\nworkspaces created from this template. Defaults to 0 (disabled).",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -23732,6 +23736,10 @@ const docTemplate = `{
|
||||
"description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"time_til_autostop_notify_ms": {
|
||||
"description": "TimeTilAutostopNotifyMillis is the duration before the workspace's\nautostop deadline at which a reminder notification is sent. 0 disables\nthe notification.",
|
||||
"type": "integer"
|
||||
},
|
||||
"time_til_dormant_autodelete_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -25022,6 +25030,10 @@ const docTemplate = `{
|
||||
"description": "RequireActiveVersion mandates workspaces built using this template\nuse the active version of the template. This option has no\neffect on template admins.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"time_til_autostop_notify_ms": {
|
||||
"description": "TimeTilAutostopNotifyMillis allows optionally specifying the duration\nbefore the autostop deadline at which a reminder notification is sent for\nworkspaces created from this template. Defaults to 0 (disabled). Omitting\nthe field keeps the existing value.",
|
||||
"type": "integer"
|
||||
},
|
||||
"time_til_dormant_autodelete_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
Generated
+12
@@ -16671,6 +16671,10 @@
|
||||
"description": "VersionID is an in-progress or completed job to use as an initial version\nof the template.\n\nThis is required on creation to enable a user-flow of validating a\ntemplate works. There is no reason the data-model cannot support empty\ntemplates, but it doesn't make sense for users.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"time_til_autostop_notify_ms": {
|
||||
"description": "TimeTilAutostopNotifyMillis allows optionally specifying the duration\nbefore the autostop deadline at which a reminder notification is sent for\nworkspaces created from this template. Defaults to 0 (disabled).",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -21769,6 +21773,10 @@
|
||||
"description": "RequireActiveVersion mandates that workspaces are built with the active\ntemplate version.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"time_til_autostop_notify_ms": {
|
||||
"description": "TimeTilAutostopNotifyMillis is the duration before the workspace's\nautostop deadline at which a reminder notification is sent. 0 disables\nthe notification.",
|
||||
"type": "integer"
|
||||
},
|
||||
"time_til_dormant_autodelete_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -22995,6 +23003,10 @@
|
||||
"description": "RequireActiveVersion mandates workspaces built using this template\nuse the active version of the template. This option has no\neffect on template admins.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"time_til_autostop_notify_ms": {
|
||||
"description": "TimeTilAutostopNotifyMillis allows optionally specifying the duration\nbefore the autostop deadline at which a reminder notification is sent for\nworkspaces created from this template. Defaults to 0 (disabled). Omitting\nthe field keeps the existing value.",
|
||||
"type": "integer"
|
||||
},
|
||||
"time_til_dormant_autodelete_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
Generated
+4
-1
@@ -26837,7 +26837,8 @@ SET
|
||||
autostart_block_days_of_week = $9,
|
||||
failure_ttl = $10,
|
||||
time_til_dormant = $11,
|
||||
time_til_dormant_autodelete = $12
|
||||
time_til_dormant_autodelete = $12,
|
||||
time_til_autostop_notify = $13
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
@@ -26855,6 +26856,7 @@ type UpdateTemplateScheduleByIDParams struct {
|
||||
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
|
||||
TimeTilDormant int64 `db:"time_til_dormant" json:"time_til_dormant"`
|
||||
TimeTilDormantAutoDelete int64 `db:"time_til_dormant_autodelete" json:"time_til_dormant_autodelete"`
|
||||
TimeTilAutostopNotify int64 `db:"time_til_autostop_notify" json:"time_til_autostop_notify"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error {
|
||||
@@ -26871,6 +26873,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
|
||||
arg.FailureTTL,
|
||||
arg.TimeTilDormant,
|
||||
arg.TimeTilDormantAutoDelete,
|
||||
arg.TimeTilAutostopNotify,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -193,7 +193,8 @@ SET
|
||||
autostart_block_days_of_week = $9,
|
||||
failure_ttl = $10,
|
||||
time_til_dormant = $11,
|
||||
time_til_dormant_autodelete = $12
|
||||
time_til_dormant_autodelete = $12,
|
||||
time_til_autostop_notify = $13
|
||||
WHERE
|
||||
id = $1
|
||||
;
|
||||
|
||||
@@ -123,6 +123,10 @@ type TemplateScheduleOptions struct {
|
||||
// ActivityBump dictates the duration to bump the workspace's deadline by if
|
||||
// Coder detects activity from the user. A value of 0 means no bumping.
|
||||
ActivityBump time.Duration
|
||||
// TimeTilAutostopNotify dictates how long before the workspace's autostop
|
||||
// deadline a reminder notification should be sent. A value of 0 means
|
||||
// disabled.
|
||||
TimeTilAutostopNotify time.Duration
|
||||
// AutostopRequirement dictates when the workspace must be restarted. This
|
||||
// used to be handled by MaxTTL.
|
||||
AutostopRequirement TemplateAutostopRequirement
|
||||
@@ -178,10 +182,11 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te
|
||||
return TemplateScheduleOptions{
|
||||
// Disregard the values in the database, since user scheduling is an
|
||||
// enterprise feature.
|
||||
UserAutostartEnabled: true,
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
ActivityBump: time.Duration(tpl.ActivityBump),
|
||||
UserAutostartEnabled: true,
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
ActivityBump: time.Duration(tpl.ActivityBump),
|
||||
TimeTilAutostopNotify: time.Duration(tpl.TimeTilAutostopNotify),
|
||||
// Disregard the values in the database, since AutostopRequirement,
|
||||
// FailureTTL, TimeTilDormant, and TimeTilDormantAutoDelete are enterprise features.
|
||||
AutostartRequirement: TemplateAutostartRequirement{
|
||||
@@ -204,7 +209,9 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
if int64(opts.DefaultTTL) == tpl.DefaultTTL && int64(opts.ActivityBump) == tpl.ActivityBump {
|
||||
if int64(opts.DefaultTTL) == tpl.DefaultTTL &&
|
||||
int64(opts.ActivityBump) == tpl.ActivityBump &&
|
||||
int64(opts.TimeTilAutostopNotify) == tpl.TimeTilAutostopNotify {
|
||||
// Avoid updating the UpdatedAt timestamp if nothing will be changed.
|
||||
return tpl, nil
|
||||
}
|
||||
@@ -212,10 +219,11 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp
|
||||
var template database.Template
|
||||
err := db.InTx(func(db database.Store) error {
|
||||
err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
|
||||
ID: tpl.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
DefaultTTL: int64(opts.DefaultTTL),
|
||||
ActivityBump: int64(opts.ActivityBump),
|
||||
ID: tpl.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
DefaultTTL: int64(opts.DefaultTTL),
|
||||
ActivityBump: int64(opts.ActivityBump),
|
||||
TimeTilAutostopNotify: int64(opts.TimeTilAutostopNotify),
|
||||
// Don't allow changing these settings, but keep the value in the DB (to
|
||||
// avoid clearing settings if the license has an issue).
|
||||
AutostopRequirementDaysOfWeek: tpl.AutostopRequirementDaysOfWeek,
|
||||
|
||||
+26
-8
@@ -303,6 +303,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
failureTTL time.Duration
|
||||
dormantTTL time.Duration
|
||||
dormantAutoDeletionTTL time.Duration
|
||||
timeTilAutostopNotify time.Duration
|
||||
)
|
||||
if createTemplate.DefaultTTLMillis != nil {
|
||||
defaultTTL = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond
|
||||
@@ -310,6 +311,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
if createTemplate.ActivityBumpMillis != nil {
|
||||
activityBump = time.Duration(*createTemplate.ActivityBumpMillis) * time.Millisecond
|
||||
}
|
||||
if createTemplate.TimeTilAutostopNotifyMillis != nil {
|
||||
timeTilAutostopNotify = time.Duration(*createTemplate.TimeTilAutostopNotifyMillis) * time.Millisecond
|
||||
}
|
||||
if createTemplate.AutostopRequirement != nil {
|
||||
autostopRequirementDaysOfWeek = createTemplate.AutostopRequirement.DaysOfWeek
|
||||
autostopRequirementWeeks = createTemplate.AutostopRequirement.Weeks
|
||||
@@ -343,6 +347,11 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||
if activityBump < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "activity_bump_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if timeTilAutostopNotify < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "time_til_autostop_notify_ms", Detail: "Must be a positive integer."})
|
||||
} else if timeTilAutostopNotify != 0 && timeTilAutostopNotify < time.Minute {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "time_til_autostop_notify_ms", Detail: "Must be 0 (disabled) or at least one minute."})
|
||||
}
|
||||
|
||||
if len(autostopRequirementDaysOfWeek) > 0 {
|
||||
autostopRequirementDaysOfWeekParsed, err = codersdk.WeekdaysToBitmap(autostopRequirementDaysOfWeek)
|
||||
@@ -458,10 +467,11 @@ 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,
|
||||
DefaultTTL: defaultTTL,
|
||||
ActivityBump: activityBump,
|
||||
UserAutostartEnabled: allowUserAutostart,
|
||||
UserAutostopEnabled: allowUserAutostop,
|
||||
DefaultTTL: defaultTTL,
|
||||
ActivityBump: activityBump,
|
||||
TimeTilAutostopNotify: timeTilAutostopNotify,
|
||||
// Some of these values are enterprise-only, but the
|
||||
// TemplateScheduleStore will handle avoiding setting them if
|
||||
// unlicensed.
|
||||
@@ -693,6 +703,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
if resolved.activityBumpMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "activity_bump_ms", Detail: "Must be a positive integer."})
|
||||
}
|
||||
if resolved.timeTilAutostopNotifyMillis < 0 {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "time_til_autostop_notify_ms", Detail: "Must be a positive integer."})
|
||||
} else if resolved.timeTilAutostopNotifyMillis != 0 && time.Duration(resolved.timeTilAutostopNotifyMillis)*time.Millisecond < time.Minute {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "time_til_autostop_notify_ms", Detail: "Must be 0 (disabled) or at least one minute."})
|
||||
}
|
||||
if resolved.autostopRequirementWeeks > schedule.MaxTemplateAutostopRequirementWeeks {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: "autostop_requirement.weeks", Detail: fmt.Sprintf("Must be less than %d.", schedule.MaxTemplateAutostopRequirementWeeks)})
|
||||
}
|
||||
@@ -793,6 +808,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defaultTTL := time.Duration(resolved.defaultTTLMillis) * time.Millisecond
|
||||
activityBump := time.Duration(resolved.activityBumpMillis) * time.Millisecond
|
||||
timeTilAutostopNotify := time.Duration(resolved.timeTilAutostopNotifyMillis) * time.Millisecond
|
||||
failureTTL := time.Duration(resolved.failureTTLMillis) * time.Millisecond
|
||||
inactivityTTL := time.Duration(resolved.timeTilDormantMillis) * time.Millisecond
|
||||
timeTilDormantAutoDelete := time.Duration(resolved.timeTilDormantAutoDeleteMillis) * time.Millisecond
|
||||
@@ -808,10 +824,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
||||
// Some of these values are enterprise-only, but the
|
||||
// TemplateScheduleStore will handle avoiding setting them if
|
||||
// unlicensed.
|
||||
UserAutostartEnabled: resolved.allowUserAutostart,
|
||||
UserAutostopEnabled: resolved.allowUserAutostop,
|
||||
DefaultTTL: defaultTTL,
|
||||
ActivityBump: activityBump,
|
||||
UserAutostartEnabled: resolved.allowUserAutostart,
|
||||
UserAutostopEnabled: resolved.allowUserAutostop,
|
||||
DefaultTTL: defaultTTL,
|
||||
ActivityBump: activityBump,
|
||||
TimeTilAutostopNotify: timeTilAutostopNotify,
|
||||
AutostopRequirement: schedule.TemplateAutostopRequirement{
|
||||
DaysOfWeek: resolved.autostopRequirementDaysOfWeekParsed,
|
||||
Weeks: resolved.autostopRequirementWeeks,
|
||||
@@ -1020,6 +1037,7 @@ func (api *API) convertTemplate(
|
||||
Icon: template.Icon,
|
||||
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
|
||||
ActivityBumpMillis: time.Duration(template.ActivityBump).Milliseconds(),
|
||||
TimeTilAutostopNotifyMillis: time.Duration(template.TimeTilAutostopNotify).Milliseconds(),
|
||||
CreatedByID: template.CreatedBy,
|
||||
CreatedByName: template.CreatedByUsername,
|
||||
AllowUserAutostart: template.AllowUserAutostart,
|
||||
|
||||
@@ -22,6 +22,7 @@ type templateMetaUpdate struct {
|
||||
icon string
|
||||
defaultTTLMillis int64
|
||||
activityBumpMillis int64
|
||||
timeTilAutostopNotifyMillis int64
|
||||
failureTTLMillis int64
|
||||
timeTilDormantMillis int64
|
||||
timeTilDormantAutoDeleteMillis int64
|
||||
@@ -73,6 +74,7 @@ func resolveTemplateMetaUpdate(
|
||||
icon: ptr.NilToDefault(req.Icon, template.Icon),
|
||||
defaultTTLMillis: ptr.NilToDefault(req.DefaultTTLMillis, time.Duration(template.DefaultTTL).Milliseconds()),
|
||||
activityBumpMillis: ptr.NilToDefault(req.ActivityBumpMillis, time.Duration(template.ActivityBump).Milliseconds()),
|
||||
timeTilAutostopNotifyMillis: ptr.NilToDefault(req.TimeTilAutostopNotifyMillis, time.Duration(template.TimeTilAutostopNotify).Milliseconds()),
|
||||
failureTTLMillis: ptr.NilToDefault(req.FailureTTLMillis, time.Duration(template.FailureTTL).Milliseconds()),
|
||||
timeTilDormantMillis: ptr.NilToDefault(req.TimeTilDormantMillis, time.Duration(template.TimeTilDormant).Milliseconds()),
|
||||
timeTilDormantAutoDeleteMillis: ptr.NilToDefault(req.TimeTilDormantAutoDeleteMillis, time.Duration(template.TimeTilDormantAutoDelete).Milliseconds()),
|
||||
|
||||
@@ -31,6 +31,7 @@ func baselineTemplate() database.Template {
|
||||
RequireActiveVersion: true,
|
||||
DefaultTTL: int64(60 * 60 * 1000 * 1000 * 1000), // 1 hour in ns
|
||||
ActivityBump: int64(30 * 60 * 1000 * 1000 * 1000), // 30 minutes in ns
|
||||
TimeTilAutostopNotify: int64(10 * 60 * 1000 * 1000 * 1000), // 10 minutes in ns
|
||||
FailureTTL: int64(120 * 60 * 1000 * 1000 * 1000), // 2 hours in ns
|
||||
TimeTilDormant: int64(240 * 60 * 1000 * 1000 * 1000), // 4 hours in ns
|
||||
TimeTilDormantAutoDelete: int64(480 * 60 * 1000 * 1000 * 1000), // 8 hours in ns
|
||||
@@ -73,6 +74,7 @@ func baselineResolved() templateMetaUpdate {
|
||||
icon: tpl.Icon,
|
||||
defaultTTLMillis: tpl.DefaultTTL / 1e6,
|
||||
activityBumpMillis: tpl.ActivityBump / 1e6,
|
||||
timeTilAutostopNotifyMillis: tpl.TimeTilAutostopNotify / 1e6,
|
||||
failureTTLMillis: tpl.FailureTTL / 1e6,
|
||||
timeTilDormantMillis: tpl.TimeTilDormant / 1e6,
|
||||
timeTilDormantAutoDeleteMillis: tpl.TimeTilDormantAutoDelete / 1e6,
|
||||
@@ -176,6 +178,20 @@ func TestResolveTemplateMetaUpdate(t *testing.T) {
|
||||
r.activityBumpMillis = 900_000
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "TimeTilAutostopNotifyMillis",
|
||||
req: codersdk.UpdateTemplateMeta{TimeTilAutostopNotifyMillis: ptr.Ref(int64(300_000))},
|
||||
expected: expected{override: func(r *templateMetaUpdate) {
|
||||
r.timeTilAutostopNotifyMillis = 300_000
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "TimeTilAutostopNotifyMillisZeroExplicit",
|
||||
req: codersdk.UpdateTemplateMeta{TimeTilAutostopNotifyMillis: ptr.Ref(int64(0))},
|
||||
expected: expected{override: func(r *templateMetaUpdate) {
|
||||
r.timeTilAutostopNotifyMillis = 0
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "AllowUserAutostart",
|
||||
req: codersdk.UpdateTemplateMeta{AllowUserAutostart: ptr.Ref(true)},
|
||||
|
||||
@@ -67,8 +67,10 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
|
||||
expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.ActivityBumpMillis = ptr.Ref((3 * time.Hour).Milliseconds())
|
||||
ctr.TimeTilAutostopNotifyMillis = ptr.Ref((5 * time.Minute).Milliseconds())
|
||||
})
|
||||
assert.Equal(t, (3 * time.Hour).Milliseconds(), expected.ActivityBumpMillis)
|
||||
assert.Equal(t, (5 * time.Minute).Milliseconds(), expected.TimeTilAutostopNotifyMillis)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
@@ -78,6 +80,7 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
assert.Equal(t, expected.Name, got.Name)
|
||||
assert.Equal(t, expected.Description, got.Description)
|
||||
assert.Equal(t, expected.ActivityBumpMillis, got.ActivityBumpMillis)
|
||||
assert.Equal(t, expected.TimeTilAutostopNotifyMillis, got.TimeTilAutostopNotifyMillis)
|
||||
assert.Equal(t, expected.UseClassicParameterFlow, false) // Current default is false
|
||||
|
||||
require.Len(t, auditor.AuditLogs(), 3)
|
||||
@@ -141,6 +144,62 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
||||
require.Contains(t, err.Error(), "default_ttl_ms: Must be a positive integer")
|
||||
})
|
||||
|
||||
t.Run("TimeTilAutostopNotifyTooLow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
_, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{
|
||||
Name: "testing",
|
||||
VersionID: version.ID,
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(int64(30_000)),
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
||||
require.Len(t, apiErr.Validations, 1)
|
||||
assert.Equal(t, "time_til_autostop_notify_ms", apiErr.Validations[0].Field)
|
||||
assert.Equal(t, "Must be 0 (disabled) or at least one minute.", apiErr.Validations[0].Detail)
|
||||
})
|
||||
|
||||
t.Run("TimeTilAutostopNotifyExactlyOneMinute", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
got, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{
|
||||
Name: "testing",
|
||||
VersionID: version.ID,
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(time.Minute.Milliseconds()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, time.Minute.Milliseconds(), got.TimeTilAutostopNotifyMillis)
|
||||
})
|
||||
|
||||
t.Run("TimeTilAutostopNotifyNegative", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
_, err := client.CreateTemplate(ctx, user.OrganizationID, codersdk.CreateTemplateRequest{
|
||||
Name: "testing",
|
||||
VersionID: version.ID,
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(int64(-1)),
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
||||
require.Len(t, apiErr.Validations, 1)
|
||||
assert.Equal(t, "time_til_autostop_notify_ms", apiErr.Validations[0].Field)
|
||||
assert.Equal(t, "Must be a positive integer.", apiErr.Validations[0].Detail)
|
||||
})
|
||||
|
||||
t.Run("NoDefaultTTL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
@@ -907,6 +966,7 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
Icon: ptr.Ref("/icon/new-icon.png"),
|
||||
DefaultTTLMillis: ptr.Ref(12 * time.Hour.Milliseconds()),
|
||||
ActivityBumpMillis: ptr.Ref(3 * time.Hour.Milliseconds()),
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(5 * time.Minute.Milliseconds()),
|
||||
AllowUserCancelWorkspaceJobs: ptr.Ref(false),
|
||||
}
|
||||
// It is unfortunate we need to sleep, but the test can fail if the
|
||||
@@ -924,6 +984,7 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
assert.Equal(t, *req.Icon, updated.Icon)
|
||||
assert.Equal(t, *req.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, *req.ActivityBumpMillis, updated.ActivityBumpMillis)
|
||||
assert.Equal(t, *req.TimeTilAutostopNotifyMillis, updated.TimeTilAutostopNotifyMillis)
|
||||
assert.False(t, *req.AllowUserCancelWorkspaceJobs)
|
||||
|
||||
// Extra paranoid: did it _really_ happen?
|
||||
@@ -936,6 +997,7 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
assert.Equal(t, *req.Icon, updated.Icon)
|
||||
assert.Equal(t, *req.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, *req.ActivityBumpMillis, updated.ActivityBumpMillis)
|
||||
assert.Equal(t, *req.TimeTilAutostopNotifyMillis, updated.TimeTilAutostopNotifyMillis)
|
||||
assert.False(t, *req.AllowUserCancelWorkspaceJobs)
|
||||
|
||||
require.Len(t, auditor.AuditLogs(), 5)
|
||||
@@ -1343,6 +1405,32 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
||||
})
|
||||
|
||||
t.Run("TimeTilAutostopNotifyPreservedWhenOmitted", 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.TimeTilAutostopNotifyMillis = ptr.Ref((5 * time.Minute).Milliseconds())
|
||||
})
|
||||
require.Equal(t, (5 * time.Minute).Milliseconds(), template.TimeTilAutostopNotifyMillis)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
// Patch an unrelated field, omitting TimeTilAutostopNotifyMillis.
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
Description: ptr.Ref("updated description"),
|
||||
}
|
||||
_, err := client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := client.Template(ctx, template.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "updated description", updated.Description)
|
||||
assert.Equal(t, template.TimeTilAutostopNotifyMillis, updated.TimeTilAutostopNotifyMillis)
|
||||
})
|
||||
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1375,6 +1463,62 @@ func TestPatchTemplateMeta(t *testing.T) {
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
})
|
||||
|
||||
t.Run("TimeTilAutostopNotifyInvalid", 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)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
// Sub-minute (non-zero) values are rejected.
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(int64(30_000)),
|
||||
}
|
||||
_, err := client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Contains(t, apiErr.Message, "Invalid request")
|
||||
require.Len(t, apiErr.Validations, 1)
|
||||
assert.Equal(t, "time_til_autostop_notify_ms", apiErr.Validations[0].Field)
|
||||
assert.Equal(t, "Must be 0 (disabled) or at least one minute.", apiErr.Validations[0].Detail)
|
||||
|
||||
// Negative values are rejected.
|
||||
req = codersdk.UpdateTemplateMeta{
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(int64(-1)),
|
||||
}
|
||||
_, err = client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Contains(t, apiErr.Message, "Invalid request")
|
||||
require.Len(t, apiErr.Validations, 1)
|
||||
assert.Equal(t, "time_til_autostop_notify_ms", apiErr.Validations[0].Field)
|
||||
assert.Equal(t, "Must be a positive integer.", apiErr.Validations[0].Detail)
|
||||
})
|
||||
|
||||
t.Run("TimeTilAutostopNotifyDisableAfterEnable", 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.TimeTilAutostopNotifyMillis = ptr.Ref((5 * time.Minute).Milliseconds())
|
||||
})
|
||||
require.Equal(t, (5 * time.Minute).Milliseconds(), template.TimeTilAutostopNotifyMillis)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
// Explicitly disable by sending 0, which must not be treated as omitted.
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
TimeTilAutostopNotifyMillis: ptr.Ref(int64(0)),
|
||||
}
|
||||
updated, err := client.UpdateTemplateMeta(ctx, template.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), updated.TimeTilAutostopNotifyMillis)
|
||||
})
|
||||
|
||||
t.Run("RemoveIcon", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -170,6 +170,10 @@ type CreateTemplateRequest struct {
|
||||
// duration for all workspaces created from this template. Defaults to 1h
|
||||
// but can be set to 0 to disable activity bumping.
|
||||
ActivityBumpMillis *int64 `json:"activity_bump_ms,omitempty"`
|
||||
// TimeTilAutostopNotifyMillis allows optionally specifying the duration
|
||||
// before the autostop deadline at which a reminder notification is sent for
|
||||
// workspaces created from this template. Defaults to 0 (disabled).
|
||||
TimeTilAutostopNotifyMillis *int64 `json:"time_til_autostop_notify_ms,omitempty"`
|
||||
// AutostopRequirement allows optionally specifying the autostop requirement
|
||||
// for workspaces created from this template. This is an enterprise feature.
|
||||
AutostopRequirement *TemplateAutostopRequirement `json:"autostop_requirement,omitempty"`
|
||||
|
||||
@@ -36,6 +36,10 @@ type Template struct {
|
||||
Icon string `json:"icon"`
|
||||
DefaultTTLMillis int64 `json:"default_ttl_ms"`
|
||||
ActivityBumpMillis int64 `json:"activity_bump_ms"`
|
||||
// TimeTilAutostopNotifyMillis is the duration before the workspace's
|
||||
// autostop deadline at which a reminder notification is sent. 0 disables
|
||||
// the notification.
|
||||
TimeTilAutostopNotifyMillis int64 `json:"time_til_autostop_notify_ms"`
|
||||
// AutostopRequirement and AutostartRequirement are enterprise features. Its
|
||||
// value is only used if your license is entitled to use the advanced template
|
||||
// scheduling feature.
|
||||
@@ -227,6 +231,11 @@ type UpdateTemplateMeta struct {
|
||||
// duration for all workspaces created from this template. Defaults to 1h
|
||||
// but can be set to 0 to disable activity bumping.
|
||||
ActivityBumpMillis *int64 `json:"activity_bump_ms,omitempty"`
|
||||
// TimeTilAutostopNotifyMillis allows optionally specifying the duration
|
||||
// before the autostop deadline at which a reminder notification is sent for
|
||||
// workspaces created from this template. Defaults to 0 (disabled). Omitting
|
||||
// the field keeps the existing value.
|
||||
TimeTilAutostopNotifyMillis *int64 `json:"time_til_autostop_notify_ms,omitempty"`
|
||||
// AutostopRequirement and AutostartRequirement can only be set if your license
|
||||
// includes the advanced template scheduling feature. If you attempt to set this
|
||||
// value while unlicensed, it will be ignored.
|
||||
|
||||
Generated
+8
-1
@@ -4778,7 +4778,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
"name": "string",
|
||||
"require_active_version": true,
|
||||
"template_use_classic_parameter_flow": true,
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1"
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"time_til_autostop_notify_ms": 0
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4807,6 +4808,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `template_use_classic_parameter_flow` | boolean | false | | Template use classic parameter flow allows optionally specifying whether the template should use the classic parameter flow. The default if unset is true, and is why `*bool` is used here. When dynamic parameters becomes the default, this will default to false. |
|
||||
|`template_version_id`|string|true||Template version ID is an in-progress or completed job to use as an initial version of the template.
|
||||
This is required on creation to enable a user-flow of validating a template works. There is no reason the data-model cannot support empty templates, but it doesn't make sense for users.|
|
||||
|`time_til_autostop_notify_ms`|integer|false||Time til autostop notify ms allows optionally specifying the duration before the autostop deadline at which a reminder notification is sent for workspaces created from this template. Defaults to 0 (disabled).|
|
||||
|
||||
## codersdk.CreateTemplateVersionDryRunRequest
|
||||
|
||||
@@ -11691,6 +11693,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -11733,6 +11736,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
| `organization_name` | string | false | | |
|
||||
| `provisioner` | string | false | | |
|
||||
| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. |
|
||||
| `time_til_autostop_notify_ms` | integer | false | | Time til autostop notify ms is the duration before the workspace's autostop deadline at which a reminder notification is sent. 0 disables the notification. |
|
||||
| `time_til_dormant_autodelete_ms` | integer | false | | |
|
||||
| `time_til_dormant_ms` | integer | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
@@ -12153,6 +12157,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -13278,6 +13283,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|
||||
"max_port_share_level": "owner",
|
||||
"name": "string",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"update_workspace_dormant_at": true,
|
||||
@@ -13308,6 +13314,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|
||||
| `max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `require_active_version` | boolean | false | | Require active version mandates workspaces built using this template use the active version of the template. This option has no effect on template admins. |
|
||||
| `time_til_autostop_notify_ms` | integer | false | | Time til autostop notify ms allows optionally specifying the duration before the autostop deadline at which a reminder notification is sent for workspaces created from this template. Defaults to 0 (disabled). Omitting the field keeps the existing value. |
|
||||
| `time_til_dormant_autodelete_ms` | integer | false | | |
|
||||
| `time_til_dormant_ms` | integer | false | | |
|
||||
| `update_workspace_dormant_at` | boolean | false | | Update workspace dormant at updates the dormant_at field of workspaces spawned from the template. This is useful for preventing dormant workspaces being immediately deleted when updating the dormant_ttl field to a new, shorter value. |
|
||||
|
||||
Generated
+1
@@ -206,6 +206,7 @@ curl -X POST http://coder-server:8080/api/v2/templatebuilder/compose/template \
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
|
||||
Generated
+11
-1
@@ -79,6 +79,7 @@ To include deprecated templates, specify `deprecated:true` in the search query.
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -138,6 +139,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|
||||
|`» organization_name`|string(url)|false|||
|
||||
|`» provisioner`|string|false|||
|
||||
|`» require_active_version`|boolean|false||Require active version mandates that workspaces are built with the active template version.|
|
||||
|`» time_til_autostop_notify_ms`|integer|false||Time til autostop notify ms is the duration before the workspace's autostop deadline at which a reminder notification is sent. 0 disables the notification.|
|
||||
|`» time_til_dormant_autodelete_ms`|integer|false|||
|
||||
|`» time_til_dormant_ms`|integer|false|||
|
||||
|`» updated_at`|string(date-time)|false|||
|
||||
@@ -199,7 +201,8 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
||||
"name": "string",
|
||||
"require_active_version": true,
|
||||
"template_use_classic_parameter_flow": true,
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1"
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"time_til_autostop_notify_ms": 0
|
||||
}
|
||||
```
|
||||
|
||||
@@ -265,6 +268,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -417,6 +421,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -839,6 +844,7 @@ To include deprecated templates, specify `deprecated:true` in the search query.
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -898,6 +904,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|
||||
|`» organization_name`|string(url)|false|||
|
||||
|`» provisioner`|string|false|||
|
||||
|`» require_active_version`|boolean|false||Require active version mandates that workspaces are built with the active template version.|
|
||||
|`» time_til_autostop_notify_ms`|integer|false||Time til autostop notify ms is the duration before the workspace's autostop deadline at which a reminder notification is sent. 0 disables the notification.|
|
||||
|`» time_til_dormant_autodelete_ms`|integer|false|||
|
||||
|`» time_til_dormant_ms`|integer|false|||
|
||||
|`» updated_at`|string(date-time)|false|||
|
||||
@@ -1043,6 +1050,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
@@ -1147,6 +1155,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \
|
||||
"max_port_share_level": "owner",
|
||||
"name": "string",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"update_workspace_dormant_at": true,
|
||||
@@ -1217,6 +1226,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \
|
||||
"organization_name": "string",
|
||||
"provisioner": "terraform",
|
||||
"require_active_version": true,
|
||||
"time_til_autostop_notify_ms": 0,
|
||||
"time_til_dormant_autodelete_ms": 0,
|
||||
"time_til_dormant_ms": 0,
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
|
||||
Generated
+8
@@ -67,6 +67,14 @@ Edit the template default time before shutdown - workspaces created from this te
|
||||
|
||||
Edit the template activity bump - workspaces created from this template will have their shutdown time bumped by this value when activity is detected. Maps to "Activity bump" in the UI.
|
||||
|
||||
### --autostop-reminder
|
||||
|
||||
| | |
|
||||
|------|-----------------------|
|
||||
| Type | <code>duration</code> |
|
||||
|
||||
Edit how long before the autostop deadline a reminder notification is sent for workspaces created from this template, in Go duration format (e.g. 1h, 30m). Set to 0 to disable.
|
||||
|
||||
### --autostart-requirement-weekdays
|
||||
|
||||
| | |
|
||||
|
||||
@@ -84,10 +84,11 @@ func (*EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.Sto
|
||||
}
|
||||
|
||||
return agpl.TemplateScheduleOptions{
|
||||
UserAutostartEnabled: tpl.AllowUserAutostart,
|
||||
UserAutostopEnabled: tpl.AllowUserAutostop,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
ActivityBump: time.Duration(tpl.ActivityBump),
|
||||
UserAutostartEnabled: tpl.AllowUserAutostart,
|
||||
UserAutostopEnabled: tpl.AllowUserAutostop,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
ActivityBump: time.Duration(tpl.ActivityBump),
|
||||
TimeTilAutostopNotify: time.Duration(tpl.TimeTilAutostopNotify),
|
||||
AutostopRequirement: agpl.TemplateAutostopRequirement{
|
||||
// #nosec G115 - Safe conversion as we've verified tpl.AutostopRequirementDaysOfWeek is <= 255
|
||||
DaysOfWeek: uint8(tpl.AutostopRequirementDaysOfWeek),
|
||||
@@ -116,6 +117,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
|
||||
if int64(opts.DefaultTTL) == tpl.DefaultTTL &&
|
||||
int64(opts.ActivityBump) == tpl.ActivityBump &&
|
||||
int64(opts.TimeTilAutostopNotify) == tpl.TimeTilAutostopNotify &&
|
||||
int16(opts.AutostopRequirement.DaysOfWeek) == tpl.AutostopRequirementDaysOfWeek &&
|
||||
opts.AutostartRequirement.DaysOfWeek == tpl.AutostartAllowedDays() &&
|
||||
opts.AutostopRequirement.Weeks == tpl.AutostopRequirementWeeks &&
|
||||
@@ -153,6 +155,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
AllowUserAutostop: opts.UserAutostopEnabled,
|
||||
DefaultTTL: int64(opts.DefaultTTL),
|
||||
ActivityBump: int64(opts.ActivityBump),
|
||||
TimeTilAutostopNotify: int64(opts.TimeTilAutostopNotify),
|
||||
AutostopRequirementDaysOfWeek: int16(opts.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: opts.AutostopRequirement.Weeks,
|
||||
// Database stores the inverse of the allowed days of the week.
|
||||
|
||||
@@ -1385,6 +1385,77 @@ func TestTemplateUpdatePrebuilds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTemplateScheduleTimeTilAutostopNotify verifies that the enterprise
|
||||
// template schedule store round-trips the TimeTilAutostopNotify field through
|
||||
// Set/Get, and that the field participates in the no-op short-circuit
|
||||
// comparison in Set. If the short-circuit compared the wrong field, an update
|
||||
// that changes only TimeTilAutostopNotify would be silently swallowed.
|
||||
func TestTemplateScheduleTimeTilAutostopNotify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
ctx = testutil.Context(t, testutil.WaitLong)
|
||||
user = dbgen.User(t, db, database.User{})
|
||||
file = dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
||||
|
||||
templateJob = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
FileID: file.ID,
|
||||
InitiatorID: user.ID,
|
||||
Tags: database.StringMap{"foo": "bar"},
|
||||
})
|
||||
templateVersion = dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
CreatedBy: user.ID,
|
||||
JobID: templateJob.ID,
|
||||
OrganizationID: templateJob.OrganizationID,
|
||||
})
|
||||
template = dbgen.Template(t, db, database.Template{
|
||||
ActiveVersionID: templateVersion.ID,
|
||||
CreatedBy: user.ID,
|
||||
OrganizationID: templateJob.OrganizationID,
|
||||
})
|
||||
)
|
||||
|
||||
// Setup the template schedule store.
|
||||
notifyEnq := notifications.NewNoopEnqueuer()
|
||||
const userQuietHoursSchedule = "CRON_TZ=UTC 0 0 * * *" // midnight UTC
|
||||
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule, true)
|
||||
require.NoError(t, err)
|
||||
userQuietHoursStorePtr := &atomic.Pointer[agplschedule.UserQuietHoursScheduleStore]{}
|
||||
userQuietHoursStorePtr.Store(&userQuietHoursStore)
|
||||
templateScheduleStore := schedule.NewEnterpriseTemplateScheduleStore(userQuietHoursStorePtr, notifyEnq, logger, nil)
|
||||
|
||||
// baseOpts is reused across Set calls so that only TimeTilAutostopNotify
|
||||
// differs between the first and second update. This is what exercises the
|
||||
// no-op short-circuit guard: every other compared field stays identical.
|
||||
baseOpts := agplschedule.TemplateScheduleOptions{
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
}
|
||||
|
||||
// Set then Get round-trip: TimeTilAutostopNotify should persist.
|
||||
firstOpts := baseOpts
|
||||
firstOpts.TimeTilAutostopNotify = time.Hour
|
||||
template, err = templateScheduleStore.Set(ctx, db, template, firstOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotOpts, err := templateScheduleStore.Get(ctx, db, template.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, time.Hour, gotOpts.TimeTilAutostopNotify)
|
||||
|
||||
// No-op short-circuit guard: change ONLY TimeTilAutostopNotify and keep all
|
||||
// other options identical. If the short-circuit compared the wrong field,
|
||||
// this update would be treated as a no-op and the value would remain 1h.
|
||||
secondOpts := baseOpts
|
||||
secondOpts.TimeTilAutostopNotify = 2 * time.Hour
|
||||
template, err = templateScheduleStore.Set(ctx, db, template, secondOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotOpts, err = templateScheduleStore.Get(ctx, db, template.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2*time.Hour, gotOpts.TimeTilAutostopNotify)
|
||||
}
|
||||
|
||||
func must[V any](v V, err error) V {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
Generated
+19
@@ -3686,6 +3686,12 @@ export interface CreateTemplateRequest {
|
||||
* but can be set to 0 to disable activity bumping.
|
||||
*/
|
||||
readonly activity_bump_ms?: number;
|
||||
/**
|
||||
* TimeTilAutostopNotifyMillis allows optionally specifying the duration
|
||||
* before the autostop deadline at which a reminder notification is sent for
|
||||
* workspaces created from this template. Defaults to 0 (disabled).
|
||||
*/
|
||||
readonly time_til_autostop_notify_ms?: number;
|
||||
/**
|
||||
* AutostopRequirement allows optionally specifying the autostop requirement
|
||||
* for workspaces created from this template. This is an enterprise feature.
|
||||
@@ -8168,6 +8174,12 @@ export interface Template {
|
||||
readonly icon: string;
|
||||
readonly default_ttl_ms: number;
|
||||
readonly activity_bump_ms: number;
|
||||
/**
|
||||
* TimeTilAutostopNotifyMillis is the duration before the workspace's
|
||||
* autostop deadline at which a reminder notification is sent. 0 disables
|
||||
* the notification.
|
||||
*/
|
||||
readonly time_til_autostop_notify_ms: number;
|
||||
/**
|
||||
* AutostopRequirement and AutostartRequirement are enterprise features. Its
|
||||
* value is only used if your license is entitled to use the advanced template
|
||||
@@ -9106,6 +9118,13 @@ export interface UpdateTemplateMeta {
|
||||
* but can be set to 0 to disable activity bumping.
|
||||
*/
|
||||
readonly activity_bump_ms?: number;
|
||||
/**
|
||||
* TimeTilAutostopNotifyMillis allows optionally specifying the duration
|
||||
* before the autostop deadline at which a reminder notification is sent for
|
||||
* workspaces created from this template. Defaults to 0 (disabled). Omitting
|
||||
* the field keeps the existing value.
|
||||
*/
|
||||
readonly time_til_autostop_notify_ms?: number;
|
||||
/**
|
||||
* AutostopRequirement and AutostartRequirement can only be set if your license
|
||||
* includes the advanced template scheduling feature. If you attempt to set this
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ import TemplateSettingsPage from "./TemplateSettingsPage";
|
||||
type FormValues = Required<
|
||||
Omit<
|
||||
UpdateTemplateMeta,
|
||||
"default_ttl_ms" | "activity_bump_ms" | "deprecation_message"
|
||||
| "default_ttl_ms"
|
||||
| "activity_bump_ms"
|
||||
| "time_til_autostop_notify_ms"
|
||||
| "deprecation_message"
|
||||
>
|
||||
>;
|
||||
|
||||
|
||||
@@ -899,6 +899,7 @@ export const MockTemplate: TypesGen.Template = {
|
||||
description: "This is a test description.",
|
||||
default_ttl_ms: 24 * 60 * 60 * 1000,
|
||||
activity_bump_ms: 1 * 60 * 60 * 1000,
|
||||
time_til_autostop_notify_ms: 0,
|
||||
autostop_requirement: {
|
||||
days_of_week: ["sunday"],
|
||||
weeks: 1,
|
||||
|
||||
Reference in New Issue
Block a user