chore: remove max_ttl from templates (#12644)

* chore: remove max_ttl from templates

Completely removing max_ttl as a feature on template scheduling. Must use other template scheduling features to achieve autostop.
This commit is contained in:
Steven Masley 2024-03-20 10:37:57 -05:00 committed by GitHub
parent d82e20152b
commit d789a60d47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 151 additions and 1304 deletions

View File

@ -29,7 +29,6 @@ func (r *RootCmd) templateCreate() *serpent.Command {
failureTTL time.Duration
dormancyThreshold time.Duration
dormancyAutoDeletion time.Duration
maxTTL time.Duration
uploadFlags templateUploadFlags
)
@ -46,7 +45,7 @@ func (r *RootCmd) templateCreate() *serpent.Command {
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 || maxTTL != 0
isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0
if isTemplateSchedulingOptionsSet || requireActiveVersion {
entitlements, err := client.Entitlements(inv.Context())
@ -58,7 +57,7 @@ func (r *RootCmd) templateCreate() *serpent.Command {
if isTemplateSchedulingOptionsSet {
if !entitlements.Features[codersdk.FeatureAdvancedTemplateScheduling].Enabled {
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl, --inactivity-ttl, or --max-ttl")
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl, or --inactivity-ttl")
}
}
@ -154,7 +153,6 @@ func (r *RootCmd) templateCreate() *serpent.Command {
VersionID: job.ID,
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()),
TimeTilDormantMillis: ptr.Ref(dormancyThreshold.Milliseconds()),
TimeTilDormantAutoDeleteMillis: ptr.Ref(dormancyAutoDeletion.Milliseconds()),
DisableEveryoneGroupAccess: disableEveryone,
@ -229,12 +227,6 @@ func (r *RootCmd) templateCreate() *serpent.Command {
Default: "0h",
Value: serpent.DurationOf(&dormancyAutoDeletion),
},
{
Flag: "max-ttl",
Description: "Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting. This is an enterprise-only feature.",
Value: serpent.DurationOf(&maxTTL),
},
{
Flag: "test.provisioner",
Description: "Customize the provisioner backend.",

View File

@ -24,7 +24,6 @@ func (r *RootCmd) templateEdit() *serpent.Command {
icon string
defaultTTL time.Duration
activityBump time.Duration
maxTTL time.Duration
autostopRequirementDaysOfWeek []string
autostopRequirementWeeks int64
autostartRequirementDaysOfWeek []string
@ -53,7 +52,6 @@ func (r *RootCmd) templateEdit() *serpent.Command {
autostopRequirementWeeks > 0 ||
!allowUserAutostart ||
!allowUserAutostop ||
maxTTL != 0 ||
failureTTL != 0 ||
dormancyThreshold != 0 ||
dormancyAutoDeletion != 0 ||
@ -69,7 +67,7 @@ func (r *RootCmd) templateEdit() *serpent.Command {
}
if requiresScheduling && !entitlements.Features[codersdk.FeatureAdvancedTemplateScheduling].Enabled {
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --max-ttl, --failure-ttl, --inactivityTTL, --allow-user-autostart=false or --allow-user-autostop=false")
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl, --inactivityTTL, --allow-user-autostart=false or --allow-user-autostop=false")
}
if requireActiveVersion {
@ -101,10 +99,6 @@ func (r *RootCmd) templateEdit() *serpent.Command {
displayName = template.DisplayName
}
if !userSetOption(inv, "max-ttl") {
maxTTL = time.Duration(template.MaxTTLMillis) * time.Millisecond
}
if !userSetOption(inv, "default-ttl") {
defaultTTL = time.Duration(template.DefaultTTLMillis) * time.Millisecond
}
@ -179,7 +173,6 @@ func (r *RootCmd) templateEdit() *serpent.Command {
Icon: icon,
DefaultTTLMillis: defaultTTL.Milliseconds(),
ActivityBumpMillis: activityBump.Milliseconds(),
MaxTTLMillis: maxTTL.Milliseconds(),
AutostopRequirement: &codersdk.TemplateAutostopRequirement{
DaysOfWeek: autostopRequirementDaysOfWeek,
Weeks: autostopRequirementWeeks,
@ -244,11 +237,6 @@ 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: "max-ttl",
Description: "Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting, regardless of user activity. This is an enterprise-only feature. Maps to \"Max lifetime\" in the UI.",
Value: serpent.DurationOf(&maxTTL),
},
{
Flag: "autostart-requirement-weekdays",
// workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.
@ -268,8 +256,6 @@ func (r *RootCmd) templateEdit() *serpent.Command {
{
Flag: "autostop-requirement-weekdays",
Description: "Edit the template autostop requirement weekdays - workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.",
// TODO(@dean): unhide when we delete max_ttl
Hidden: true,
Value: serpent.Validate(serpent.StringArrayOf(&autostopRequirementDaysOfWeek), func(value *serpent.StringArray) error {
v := value.GetSlice()
if len(v) == 1 && v[0] == "none" {
@ -285,9 +271,7 @@ func (r *RootCmd) templateEdit() *serpent.Command {
{
Flag: "autostop-requirement-weeks",
Description: "Edit the template autostop requirement weeks - workspaces created from this template must be restarted on an n-weekly basis.",
// TODO(@dean): unhide when we delete max_ttl
Hidden: true,
Value: serpent.Int64Of(&autostopRequirementWeeks),
Value: serpent.Int64Of(&autostopRequirementWeeks),
},
{
Flag: "failure-ttl",

View File

@ -552,220 +552,7 @@ func TestTemplateEdit(t *testing.T) {
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
})
})
// TODO(@dean): remove this test when we remove max_ttl
t.Run("MaxTTL", func(t *testing.T) {
t.Parallel()
t.Run("BlockedAGPL", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client)
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.DefaultTTLMillis = nil
ctr.MaxTTLMillis = nil
})
// Test the cli command.
cmdArgs := []string{
"templates",
"edit",
template.Name,
"--max-ttl", "1h",
}
inv, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, templateAdmin, root)
ctx := testutil.Context(t, testutil.WaitLong)
err := inv.WithContext(ctx).Run()
require.Error(t, err)
require.ErrorContains(t, err, "appears to be an AGPL deployment")
// Assert that the template metadata did not change.
updated, err := client.Template(context.Background(), template.ID)
require.NoError(t, err)
assert.Equal(t, template.Name, updated.Name)
assert.Equal(t, template.Description, updated.Description)
assert.Equal(t, template.Icon, updated.Icon)
assert.Equal(t, template.DisplayName, updated.DisplayName)
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis)
})
t.Run("BlockedNotEntitled", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client)
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.DefaultTTLMillis = nil
ctr.MaxTTLMillis = nil
})
// Make a proxy server that will return a valid entitlements
// response, but without advanced scheduling entitlement.
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v2/entitlements" {
res := codersdk.Entitlements{
Features: map[codersdk.FeatureName]codersdk.Feature{},
Warnings: []string{},
Errors: []string{},
HasLicense: true,
Trial: true,
RequireTelemetry: false,
}
for _, feature := range codersdk.FeatureNames {
res.Features[feature] = codersdk.Feature{
Entitlement: codersdk.EntitlementNotEntitled,
Enabled: false,
Limit: nil,
Actual: nil,
}
}
httpapi.Write(r.Context(), w, http.StatusOK, res)
return
}
// Otherwise, proxy the request to the real API server.
rp := httputil.NewSingleHostReverseProxy(client.URL)
tp := &http.Transport{}
defer tp.CloseIdleConnections()
rp.Transport = tp
rp.ServeHTTP(w, r)
}))
defer proxy.Close()
// Create a new client that uses the proxy server.
proxyURL, err := url.Parse(proxy.URL)
require.NoError(t, err)
proxyClient := codersdk.New(proxyURL)
proxyClient.SetSessionToken(templateAdmin.SessionToken())
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
// Test the cli command.
cmdArgs := []string{
"templates",
"edit",
template.Name,
"--max-ttl", "1h",
}
inv, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, proxyClient, root)
ctx := testutil.Context(t, testutil.WaitLong)
err = inv.WithContext(ctx).Run()
require.Error(t, err)
require.ErrorContains(t, err, "license is not entitled")
// Assert that the template metadata did not change.
updated, err := client.Template(context.Background(), template.ID)
require.NoError(t, err)
assert.Equal(t, template.Name, updated.Name)
assert.Equal(t, template.Description, updated.Description)
assert.Equal(t, template.Icon, updated.Icon)
assert.Equal(t, template.DisplayName, updated.DisplayName)
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis)
})
t.Run("Entitled", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client)
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.DefaultTTLMillis = nil
ctr.MaxTTLMillis = nil
})
// Make a proxy server that will return a valid entitlements
// response, including a valid advanced scheduling entitlement.
var updateTemplateCalled int64
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v2/entitlements" {
res := codersdk.Entitlements{
Features: map[codersdk.FeatureName]codersdk.Feature{},
Warnings: []string{},
Errors: []string{},
HasLicense: true,
Trial: true,
RequireTelemetry: false,
}
for _, feature := range codersdk.FeatureNames {
var one int64 = 1
res.Features[feature] = codersdk.Feature{
Entitlement: codersdk.EntitlementNotEntitled,
Enabled: true,
Limit: &one,
Actual: &one,
}
}
httpapi.Write(r.Context(), w, http.StatusOK, res)
return
}
if strings.HasPrefix(r.URL.Path, "/api/v2/templates/") {
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
_ = r.Body.Close()
var req codersdk.UpdateTemplateMeta
err = json.Unmarshal(body, &req)
require.NoError(t, err)
assert.Equal(t, time.Hour.Milliseconds(), req.MaxTTLMillis)
r.Body = io.NopCloser(bytes.NewReader(body))
atomic.AddInt64(&updateTemplateCalled, 1)
// We still want to call the real route.
}
// Otherwise, proxy the request to the real API server.
rp := httputil.NewSingleHostReverseProxy(client.URL)
tp := &http.Transport{}
defer tp.CloseIdleConnections()
rp.Transport = tp
rp.ServeHTTP(w, r)
}))
defer proxy.Close()
// Create a new client that uses the proxy server.
proxyURL, err := url.Parse(proxy.URL)
require.NoError(t, err)
proxyClient := codersdk.New(proxyURL)
proxyClient.SetSessionToken(templateAdmin.SessionToken())
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
// Test the cli command.
cmdArgs := []string{
"templates",
"edit",
template.Name,
"--max-ttl", "1h",
}
inv, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, proxyClient, root)
ctx := testutil.Context(t, testutil.WaitLong)
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
require.EqualValues(t, 1, atomic.LoadInt64(&updateTemplateCalled))
// Assert that the template metadata did not change. We verify the
// correct request gets sent to the server already.
updated, err := client.Template(context.Background(), template.ID)
require.NoError(t, err)
assert.Equal(t, template.Name, updated.Name)
assert.Equal(t, template.Description, updated.Description)
assert.Equal(t, template.Icon, updated.Icon)
assert.Equal(t, template.DisplayName, updated.DisplayName)
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis)
})
})
t.Run("AllowUserScheduling", func(t *testing.T) {
t.Parallel()
t.Run("BlockedAGPL", func(t *testing.T) {

View File

@ -467,7 +467,7 @@ telemetrywhen required by your organization's security policy.
USER QUIET HOURS SCHEDULE OPTIONS:
Allow users to set quiet hours schedules each day for workspaces to avoid
workspaces stopping during the day due to template max TTL.
workspaces stopping during the day due to template scheduling.
--allow-custom-quiet-hours bool, $CODER_ALLOW_CUSTOM_QUIET_HOURS (default: true)
Allow users to set their own quiet hours schedule for workspaces to

View File

@ -35,11 +35,6 @@ OPTIONS:
Ignore warnings about not having a .terraform.lock.hcl file present in
the template.
--max-ttl duration
Edit the template maximum time before shutdown - workspaces created
from this template must shutdown within the given duration after
starting. This is an enterprise-only feature.
-m, --message string
Specify a message describing the changes in this version of the
template. Messages longer than 72 characters will be displayed as

View File

@ -28,6 +28,16 @@ OPTIONS:
this value for the template (and allow autostart on all days), pass
'all'.
--autostop-requirement-weekdays string-array
Edit the template autostop requirement weekdays - workspaces created
from this template must be restarted on the given weekdays. To unset
this value for the template (and disable the autostop requirement for
the template), pass 'none'.
--autostop-requirement-weeks int
Edit the template autostop requirement weeks - workspaces created from
this template must be restarted on an n-weekly basis.
--default-ttl duration
Edit the template default time before shutdown - workspaces created
from this template default to this value. Maps to "Default autostop"
@ -62,12 +72,6 @@ OPTIONS:
--icon string
Edit the template icon path.
--max-ttl duration
Edit the template maximum time before shutdown - workspaces created
from this template must shutdown within the given duration after
starting, regardless of user activity. This is an enterprise-only
feature. Maps to "Max lifetime" in the UI.
--name string
Edit the template name.

View File

@ -462,7 +462,7 @@ externalAuthProviders: []
# (default: <unset>, type: string)
wgtunnelHost: ""
# Allow users to set quiet hours schedules each day for workspaces to avoid
# workspaces stopping during the day due to template max TTL.
# workspaces stopping during the day due to template scheduling.
userQuietHoursSchedule:
# The default daily cron schedule applied to users that haven't set a custom quiet
# hours schedule themselves. The quiet hours schedule determines when workspaces

View File

@ -31,10 +31,6 @@ func TestWorkspaceActivityBump(t *testing.T) {
setupActivityTest := func(t *testing.T, deadline ...time.Duration) (client *codersdk.Client, workspace codersdk.Workspace, assertBumped func(want bool)) {
t.Helper()
const ttl = time.Hour
maxTTL := time.Duration(0)
if len(deadline) > 0 {
maxTTL = deadline[0]
}
db, pubsub := dbtestutil.NewDB(t)
client = coderdtest.New(t, &coderdtest.Options{
@ -73,8 +69,8 @@ func TestWorkspaceActivityBump(t *testing.T) {
var maxDeadline time.Time
// Update the max deadline.
if maxTTL != 0 {
maxDeadline = dbtime.Now().Add(maxTTL)
if len(deadline) > 0 {
maxDeadline = dbtime.Now().Add(deadline[0])
}
err := db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
@ -99,9 +95,9 @@ func TestWorkspaceActivityBump(t *testing.T) {
)
firstDeadline := workspace.LatestBuild.Deadline.Time
if maxTTL != 0 {
if !maxDeadline.IsZero() {
require.WithinDuration(t,
time.Now().Add(maxTTL),
maxDeadline,
workspace.LatestBuild.MaxDeadline.Time,
testutil.WaitMedium,
)
@ -218,6 +214,6 @@ func TestWorkspaceActivityBump(t *testing.T) {
require.NoError(t, err)
_ = sshConn.Close()
assertBumped(true) // also asserts max ttl not exceeded
assertBumped(true)
})
}

16
coderd/apidoc/docs.go generated
View File

@ -8739,7 +8739,7 @@ const docTemplate = `{
]
},
"autostop_requirement": {
"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.",
"description": "AutostopRequirement allows optionally specifying the autostop requirement\nfor workspaces created from this template. This is an enterprise feature.",
"allOf": [
{
"$ref": "#/definitions/codersdk.TemplateAutostopRequirement"
@ -8778,10 +8778,6 @@ const docTemplate = `{
"description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.",
"type": "string"
},
"max_ttl_ms": {
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured\nOnly one of MaxTTLMillis or AutostopRequirement can be specified.",
"type": "integer"
},
"name": {
"description": "Name is the name of the template.",
"type": "string"
@ -11601,10 +11597,6 @@ const docTemplate = `{
"max_port_share_level": {
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
},
"max_ttl_ms": {
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured",
"type": "integer"
},
"name": {
"type": "string"
},
@ -11631,10 +11623,6 @@ 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"
}
}
},
@ -12367,7 +12355,7 @@ const docTemplate = `{
],
"properties": {
"schedule": {
"description": "Schedule is a cron expression that defines when the user's quiet hours\nwindow is. Schedule must not be empty. For new users, the schedule is set\nto 2am in their browser or computer's timezone. The schedule denotes the\nbeginning of a 4 hour window where the workspace is allowed to\nautomatically stop or restart due to maintenance or template max TTL.\n\nThe schedule must be daily with a single time, and should have a timezone\nspecified via a CRON_TZ prefix (otherwise UTC will be used).\n\nIf the schedule is empty, the user will be updated to use the default\nschedule.",
"description": "Schedule is a cron expression that defines when the user's quiet hours\nwindow is. Schedule must not be empty. For new users, the schedule is set\nto 2am in their browser or computer's timezone. The schedule denotes the\nbeginning of a 4 hour window where the workspace is allowed to\nautomatically stop or restart due to maintenance or template schedule.\n\nThe schedule must be daily with a single time, and should have a timezone\nspecified via a CRON_TZ prefix (otherwise UTC will be used).\n\nIf the schedule is empty, the user will be updated to use the default\nschedule.",
"type": "string"
}
}

View File

@ -7779,7 +7779,7 @@
]
},
"autostop_requirement": {
"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.",
"description": "AutostopRequirement allows optionally specifying the autostop requirement\nfor workspaces created from this template. This is an enterprise feature.",
"allOf": [
{
"$ref": "#/definitions/codersdk.TemplateAutostopRequirement"
@ -7818,10 +7818,6 @@
"description": "Icon is a relative path or external URL that specifies\nan icon to be displayed in the dashboard.",
"type": "string"
},
"max_ttl_ms": {
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured\nOnly one of MaxTTLMillis or AutostopRequirement can be specified.",
"type": "integer"
},
"name": {
"description": "Name is the name of the template.",
"type": "string"
@ -10481,10 +10477,6 @@
"max_port_share_level": {
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
},
"max_ttl_ms": {
"description": "TODO(@dean): remove max_ttl once autostop_requirement is matured",
"type": "integer"
},
"name": {
"type": "string"
},
@ -10509,10 +10501,6 @@
"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"
}
}
},
@ -11196,7 +11184,7 @@
"required": ["schedule"],
"properties": {
"schedule": {
"description": "Schedule is a cron expression that defines when the user's quiet hours\nwindow is. Schedule must not be empty. For new users, the schedule is set\nto 2am in their browser or computer's timezone. The schedule denotes the\nbeginning of a 4 hour window where the workspace is allowed to\nautomatically stop or restart due to maintenance or template max TTL.\n\nThe schedule must be daily with a single time, and should have a timezone\nspecified via a CRON_TZ prefix (otherwise UTC will be used).\n\nIf the schedule is empty, the user will be updated to use the default\nschedule.",
"description": "Schedule is a cron expression that defines when the user's quiet hours\nwindow is. Schedule must not be empty. For new users, the schedule is set\nto 2am in their browser or computer's timezone. The schedule denotes the\nbeginning of a 4 hour window where the workspace is allowed to\nautomatically stop or restart due to maintenance or template schedule.\n\nThe schedule must be daily with a single time, and should have a timezone\nspecified via a CRON_TZ prefix (otherwise UTC will be used).\n\nIf the schedule is empty, the user will be updated to use the default\nschedule.",
"type": "string"
}
}

View File

@ -6961,8 +6961,6 @@ func (q *FakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database
tpl.UpdatedAt = dbtime.Now()
tpl.DefaultTTL = arg.DefaultTTL
tpl.ActivityBump = arg.ActivityBump
tpl.UseMaxTtl = arg.UseMaxTtl
tpl.MaxTTL = arg.MaxTTL
tpl.AutostopRequirementDaysOfWeek = arg.AutostopRequirementDaysOfWeek
tpl.AutostopRequirementWeeks = arg.AutostopRequirementWeeks
tpl.AutostartBlockDaysOfWeek = arg.AutostartBlockDaysOfWeek

View File

@ -907,7 +907,6 @@ CREATE TABLE templates (
group_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
display_name character varying(64) DEFAULT ''::character varying NOT NULL,
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,
failure_ttl bigint DEFAULT 0 NOT NULL,
@ -918,7 +917,6 @@ CREATE TABLE templates (
autostart_block_days_of_week smallint DEFAULT 0 NOT NULL,
require_active_version boolean DEFAULT false NOT NULL,
deprecated text DEFAULT ''::text NOT NULL,
use_max_ttl boolean DEFAULT false NOT NULL,
activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL,
max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL
);
@ -958,7 +956,6 @@ CREATE VIEW template_with_users AS
templates.group_acl,
templates.display_name,
templates.allow_user_cancel_workspace_jobs,
templates.max_ttl,
templates.allow_user_autostart,
templates.allow_user_autostop,
templates.failure_ttl,
@ -969,7 +966,6 @@ CREATE VIEW template_with_users AS
templates.autostart_block_days_of_week,
templates.require_active_version,
templates.deprecated,
templates.use_max_ttl,
templates.activity_bump,
templates.max_port_sharing_level,
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,

View File

@ -0,0 +1,21 @@
-- Update the template_with_users view by recreating it.
DROP VIEW template_with_users;
ALTER TABLE "templates" ADD COLUMN "max_ttl" bigint DEFAULT '0'::bigint NOT NULL;
-- Most templates should have this set to false by now.
ALTER TABLE templates ADD COLUMN use_max_ttl boolean NOT NULL DEFAULT false;
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.';

View File

@ -0,0 +1,20 @@
-- Update the template_with_users view by recreating it.
DROP VIEW template_with_users;
ALTER TABLE templates DROP COLUMN "max_ttl";
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.';

View File

@ -78,7 +78,6 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
@ -89,7 +88,6 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
&i.AutostartBlockDaysOfWeek,
&i.RequireActiveVersion,
&i.Deprecated,
&i.UseMaxTtl,
&i.ActivityBump,
&i.MaxPortSharingLevel,
&i.CreatedByAvatarURL,

View File

@ -2083,7 +2083,6 @@ type Template struct {
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
DisplayName string `db:"display_name" json:"display_name"`
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
MaxTTL int64 `db:"max_ttl" json:"max_ttl"`
AllowUserAutostart bool `db:"allow_user_autostart" json:"allow_user_autostart"`
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
FailureTTL int64 `db:"failure_ttl" json:"failure_ttl"`
@ -2094,7 +2093,6 @@ 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"`
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"`
@ -2120,8 +2118,7 @@ type TemplateTable struct {
// Display name is a custom, human-friendly template name that user can set.
DisplayName string `db:"display_name" json:"display_name"`
// Allow users to cancel in-progress workspace jobs.
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
MaxTTL int64 `db:"max_ttl" json:"max_ttl"`
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
// 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).
@ -2138,7 +2135,6 @@ 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"`
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"`
}

View File

@ -6056,7 +6056,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, use_max_ttl, activity_bump, max_port_sharing_level, 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, 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, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username
FROM
template_with_users
WHERE
@ -6085,7 +6085,6 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
@ -6096,7 +6095,6 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
&i.AutostartBlockDaysOfWeek,
&i.RequireActiveVersion,
&i.Deprecated,
&i.UseMaxTtl,
&i.ActivityBump,
&i.MaxPortSharingLevel,
&i.CreatedByAvatarURL,
@ -6107,7 +6105,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, use_max_ttl, activity_bump, max_port_sharing_level, 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, 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, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username
FROM
template_with_users AS templates
WHERE
@ -6144,7 +6142,6 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
@ -6155,7 +6152,6 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
&i.AutostartBlockDaysOfWeek,
&i.RequireActiveVersion,
&i.Deprecated,
&i.UseMaxTtl,
&i.ActivityBump,
&i.MaxPortSharingLevel,
&i.CreatedByAvatarURL,
@ -6165,7 +6161,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, use_max_ttl, activity_bump, max_port_sharing_level, 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, 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, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username FROM template_with_users AS templates
ORDER BY (name, id) ASC
`
@ -6195,7 +6191,6 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
@ -6206,7 +6201,6 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
&i.AutostartBlockDaysOfWeek,
&i.RequireActiveVersion,
&i.Deprecated,
&i.UseMaxTtl,
&i.ActivityBump,
&i.MaxPortSharingLevel,
&i.CreatedByAvatarURL,
@ -6227,7 +6221,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, use_max_ttl, activity_bump, max_port_sharing_level, 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, 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, activity_bump, max_port_sharing_level, created_by_avatar_url, created_by_username
FROM
template_with_users AS templates
WHERE
@ -6307,7 +6301,6 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
&i.GroupACL,
&i.DisplayName,
&i.AllowUserCancelWorkspaceJobs,
&i.MaxTTL,
&i.AllowUserAutostart,
&i.AllowUserAutostop,
&i.FailureTTL,
@ -6318,7 +6311,6 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
&i.AutostartBlockDaysOfWeek,
&i.RequireActiveVersion,
&i.Deprecated,
&i.UseMaxTtl,
&i.ActivityBump,
&i.MaxPortSharingLevel,
&i.CreatedByAvatarURL,
@ -6535,14 +6527,12 @@ SET
allow_user_autostop = $4,
default_ttl = $5,
activity_bump = $6,
use_max_ttl = $7,
max_ttl = $8,
autostop_requirement_days_of_week = $9,
autostop_requirement_weeks = $10,
autostart_block_days_of_week = $11,
failure_ttl = $12,
time_til_dormant = $13,
time_til_dormant_autodelete = $14
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
WHERE
id = $1
`
@ -6554,8 +6544,6 @@ type UpdateTemplateScheduleByIDParams struct {
AllowUserAutostop bool `db:"allow_user_autostop" json:"allow_user_autostop"`
DefaultTTL int64 `db:"default_ttl" json:"default_ttl"`
ActivityBump int64 `db:"activity_bump" json:"activity_bump"`
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"`
AutostartBlockDaysOfWeek int16 `db:"autostart_block_days_of_week" json:"autostart_block_days_of_week"`
@ -6572,8 +6560,6 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
arg.AllowUserAutostop,
arg.DefaultTTL,
arg.ActivityBump,
arg.UseMaxTtl,
arg.MaxTTL,
arg.AutostopRequirementDaysOfWeek,
arg.AutostopRequirementWeeks,
arg.AutostartBlockDaysOfWeek,
@ -11907,7 +11893,7 @@ LEFT JOIN LATERAL (
) latest_build ON TRUE
LEFT JOIN LATERAL (
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, activity_bump, max_port_sharing_level
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, 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, activity_bump, max_port_sharing_level
FROM
templates
WHERE

View File

@ -132,14 +132,12 @@ SET
allow_user_autostop = $4,
default_ttl = $5,
activity_bump = $6,
use_max_ttl = $7,
max_ttl = $8,
autostop_requirement_days_of_week = $9,
autostop_requirement_weeks = $10,
autostart_block_days_of_week = $11,
failure_ttl = $12,
time_til_dormant = $13,
time_til_dormant_autodelete = $14
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
WHERE
id = $1
;

View File

@ -88,8 +88,6 @@ sql:
group_acl: GroupACL
troubleshooting_url: TroubleshootingURL
default_ttl: DefaultTTL
max_ttl: MaxTTL
template_max_ttl: TemplateMaxTTL
motd_file: MOTDFile
uuid: UUID
failure_ttl: FailureTTL

View File

@ -1039,248 +1039,6 @@ func TestCompleteJob(t *testing.T) {
require.False(t, job.Error.Valid)
})
// TODO(@dean): remove this legacy test for MaxTTL
t.Run("WorkspaceBuildLegacy", func(t *testing.T) {
t.Parallel()
cases := []struct {
name string
templateAllowAutostop bool
templateDefaultTTL time.Duration
templateMaxTTL time.Duration
workspaceTTL time.Duration
transition database.WorkspaceTransition
// The TTL is actually a deadline time on the workspace_build row,
// so during the test this will be compared to be within 15 seconds
// of the expected value.
expectedTTL time.Duration
expectedMaxTTL time.Duration
}{
{
name: "OK",
templateAllowAutostop: true,
templateDefaultTTL: 0,
templateMaxTTL: 0,
workspaceTTL: 0,
transition: database.WorkspaceTransitionStart,
expectedTTL: 0,
expectedMaxTTL: 0,
},
{
name: "Delete",
templateAllowAutostop: true,
templateDefaultTTL: 0,
templateMaxTTL: 0,
workspaceTTL: 0,
transition: database.WorkspaceTransitionDelete,
expectedTTL: 0,
expectedMaxTTL: 0,
},
{
name: "WorkspaceTTL",
templateAllowAutostop: true,
templateDefaultTTL: 0,
templateMaxTTL: 0,
workspaceTTL: time.Hour,
transition: database.WorkspaceTransitionStart,
expectedTTL: time.Hour,
expectedMaxTTL: 0,
},
{
name: "TemplateDefaultTTLIgnored",
templateAllowAutostop: true,
templateDefaultTTL: time.Hour,
templateMaxTTL: 0,
workspaceTTL: 0,
transition: database.WorkspaceTransitionStart,
expectedTTL: 0,
expectedMaxTTL: 0,
},
{
name: "WorkspaceTTLOverridesTemplateDefaultTTL",
templateAllowAutostop: true,
templateDefaultTTL: 2 * time.Hour,
templateMaxTTL: 0,
workspaceTTL: time.Hour,
transition: database.WorkspaceTransitionStart,
expectedTTL: time.Hour,
expectedMaxTTL: 0,
},
{
name: "TemplateMaxTTL",
templateAllowAutostop: true,
templateDefaultTTL: 0,
templateMaxTTL: time.Hour,
workspaceTTL: 0,
transition: database.WorkspaceTransitionStart,
expectedTTL: time.Hour,
expectedMaxTTL: time.Hour,
},
{
name: "TemplateMaxTTLOverridesWorkspaceTTL",
templateAllowAutostop: true,
templateDefaultTTL: 0,
templateMaxTTL: 2 * time.Hour,
workspaceTTL: 3 * time.Hour,
transition: database.WorkspaceTransitionStart,
expectedTTL: 2 * time.Hour,
expectedMaxTTL: 2 * time.Hour,
},
{
name: "TemplateMaxTTLOverridesTemplateDefaultTTL",
templateAllowAutostop: true,
templateDefaultTTL: 3 * time.Hour,
templateMaxTTL: 2 * time.Hour,
workspaceTTL: 0,
transition: database.WorkspaceTransitionStart,
expectedTTL: 2 * time.Hour,
expectedMaxTTL: 2 * time.Hour,
},
{
name: "TemplateBlockWorkspaceTTL",
templateAllowAutostop: false,
templateDefaultTTL: 3 * time.Hour,
templateMaxTTL: 6 * time.Hour,
workspaceTTL: 4 * time.Hour,
transition: database.WorkspaceTransitionStart,
expectedTTL: 3 * time.Hour,
expectedMaxTTL: 6 * time.Hour,
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
tss := &atomic.Pointer[schedule.TemplateScheduleStore]{}
srv, db, ps, pd := setup(t, false, &overrides{templateScheduleStore: tss})
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,
UseMaxTTL: true,
}, nil
},
}
tss.Store(&store)
user := dbgen.User(t, db, database.User{})
template := dbgen.Template(t, db, database.Template{
Name: "template",
Provisioner: database.ProvisionerTypeEcho,
OrganizationID: pd.OrganizationID,
})
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
OrganizationID: pd.OrganizationID,
TemplateID: uuid.NullUUID{
UUID: template.ID,
Valid: true,
},
JobID: uuid.New(),
})
err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
ID: template.ID,
UpdatedAt: dbtime.Now(),
AllowUserAutostart: c.templateAllowAutostop,
DefaultTTL: int64(c.templateDefaultTTL),
MaxTTL: int64(c.templateMaxTTL),
})
require.NoError(t, err)
file := dbgen.File(t, db, database.File{CreatedBy: user.ID})
workspaceTTL := sql.NullInt64{}
if c.workspaceTTL != 0 {
workspaceTTL = sql.NullInt64{
Int64: int64(c.workspaceTTL),
Valid: true,
}
}
workspace := dbgen.Workspace(t, db, database.Workspace{
TemplateID: template.ID,
Ttl: workspaceTTL,
OrganizationID: pd.OrganizationID,
})
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
WorkspaceID: workspace.ID,
TemplateVersionID: version.ID,
Transition: c.transition,
Reason: database.BuildReasonInitiator,
})
job := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
OrganizationID: pd.OrganizationID,
FileID: file.ID,
Type: database.ProvisionerJobTypeWorkspaceBuild,
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
WorkspaceBuildID: build.ID,
})),
})
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
OrganizationID: pd.OrganizationID,
WorkerID: uuid.NullUUID{
UUID: pd.ID,
Valid: true,
},
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
})
require.NoError(t, err)
publishedWorkspace := make(chan struct{})
closeWorkspaceSubscribe, err := ps.Subscribe(codersdk.WorkspaceNotifyChannel(build.WorkspaceID), func(_ context.Context, _ []byte) {
close(publishedWorkspace)
})
require.NoError(t, err)
defer closeWorkspaceSubscribe()
publishedLogs := make(chan struct{})
closeLogsSubscribe, err := ps.Subscribe(provisionersdk.ProvisionerJobLogsNotifyChannel(job.ID), func(_ context.Context, _ []byte) {
close(publishedLogs)
})
require.NoError(t, err)
defer closeLogsSubscribe()
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
JobId: job.ID.String(),
Type: &proto.CompletedJob_WorkspaceBuild_{
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
State: []byte{},
Resources: []*sdkproto.Resource{{
Name: "example",
Type: "aws_instance",
}},
},
},
})
require.NoError(t, err)
<-publishedWorkspace
<-publishedLogs
workspace, err = db.GetWorkspaceByID(ctx, workspace.ID)
require.NoError(t, err)
require.Equal(t, c.transition == database.WorkspaceTransitionDelete, workspace.Deleted)
workspaceBuild, err := db.GetWorkspaceBuildByID(ctx, build.ID)
require.NoError(t, err)
if c.expectedTTL == 0 {
require.True(t, workspaceBuild.Deadline.IsZero())
} else {
require.WithinDuration(t, time.Now().Add(c.expectedTTL), workspaceBuild.Deadline, 15*time.Second, "deadline does not match expected")
}
if c.expectedMaxTTL == 0 {
require.True(t, workspaceBuild.MaxDeadline.IsZero())
} else {
require.WithinDuration(t, time.Now().Add(c.expectedMaxTTL), workspaceBuild.MaxDeadline, 15*time.Second, "max deadline does not match expected")
require.GreaterOrEqual(t, workspaceBuild.MaxDeadline.Unix(), workspaceBuild.Deadline.Unix(), "max deadline is smaller than deadline")
}
})
}
})
t.Run("WorkspaceBuild", func(t *testing.T) {
t.Parallel()
@ -1386,7 +1144,6 @@ func TestCompleteJob(t *testing.T) {
UserAutostartEnabled: false,
UserAutostopEnabled: true,
DefaultTTL: 0,
UseMaxTTL: false,
AutostopRequirement: c.templateAutostopRequirement,
}, nil
},

View File

@ -110,14 +110,8 @@ 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.
if templateSchedule.UseMaxTTL && templateSchedule.MaxTTL > 0 {
autostop.MaxDeadline = now.Add(templateSchedule.MaxTTL)
}
// Otherwise, use the autostop_requirement algorithm.
if !templateSchedule.UseMaxTTL && templateSchedule.AutostopRequirement.DaysOfWeek != 0 {
if templateSchedule.AutostopRequirement.DaysOfWeek != 0 {
// The template has a autostop requirement, so determine the max deadline
// of this workspace build.

View File

@ -70,13 +70,10 @@ func TestCalculateAutoStop(t *testing.T) {
t.Log("saturdayMidnightAfterDstOut", saturdayMidnightAfterDstOut)
cases := []struct {
name string
now time.Time
templateAllowAutostop bool
templateDefaultTTL time.Duration
// TODO(@dean): remove max_ttl tests
useMaxTTL bool
templateMaxTTL time.Duration
name string
now time.Time
templateAllowAutostop bool
templateDefaultTTL time.Duration
templateAutostopRequirement schedule.TemplateAutostopRequirement
userQuietHoursSchedule string
// workspaceTTL is usually copied from the template's TTL when the
@ -367,40 +364,6 @@ func TestCalculateAutoStop(t *testing.T) {
// expectedDeadline is copied from expectedMaxDeadline.
expectedMaxDeadline: dstOutQuietHoursExpectedTime,
},
// TODO(@dean): remove max_ttl tests
{
name: "AutostopRequirementIgnoresMaxTTL",
now: fridayEveningSydney.In(time.UTC),
templateAllowAutostop: false,
templateDefaultTTL: 0,
useMaxTTL: false,
templateMaxTTL: time.Hour, // should be ignored
userQuietHoursSchedule: sydneyQuietHours,
templateAutostopRequirement: schedule.TemplateAutostopRequirement{
DaysOfWeek: 0b00100000, // Saturday
Weeks: 0, // weekly
},
workspaceTTL: 0,
// expectedDeadline is copied from expectedMaxDeadline.
expectedMaxDeadline: saturdayMidnightSydney.In(time.UTC),
},
{
name: "MaxTTLIgnoresAutostopRequirement",
now: fridayEveningSydney.In(time.UTC),
templateAllowAutostop: false,
templateDefaultTTL: 0,
useMaxTTL: true,
templateMaxTTL: time.Hour, // should NOT be ignored
userQuietHoursSchedule: sydneyQuietHours,
templateAutostopRequirement: schedule.TemplateAutostopRequirement{
DaysOfWeek: 0b00100000, // Saturday
Weeks: 0, // weekly
},
workspaceTTL: 0,
// expectedDeadline is copied from expectedMaxDeadline.
expectedMaxDeadline: fridayEveningSydney.Add(time.Hour).In(time.UTC),
},
}
for _, c := range cases {
@ -418,8 +381,6 @@ func TestCalculateAutoStop(t *testing.T) {
UserAutostartEnabled: false,
UserAutostopEnabled: c.templateAllowAutostop,
DefaultTTL: c.templateDefaultTTL,
MaxTTL: c.templateMaxTTL,
UseMaxTTL: c.useMaxTTL,
AutostopRequirement: c.templateAutostopRequirement,
}, nil
},

View File

@ -120,12 +120,6 @@ 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 `json:"activity_bump"`
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
UseMaxTTL bool
// AutostopRequirement dictates when the workspace must be restarted. This
// used to be handled by MaxTTL.
AutostopRequirement TemplateAutostopRequirement `json:"autostop_requirement"`
@ -187,8 +181,6 @@ func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, te
ActivityBump: time.Duration(tpl.ActivityBump),
// Disregard the values in the database, since AutostopRequirement,
// FailureTTL, TimeTilDormant, and TimeTilDormantAutoDelete are enterprise features.
UseMaxTTL: false,
MaxTTL: 0,
AutostartRequirement: TemplateAutostartRequirement{
// Default to allowing all days for AGPL
DaysOfWeek: 0b01111111,
@ -223,8 +215,6 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp
ActivityBump: int64(opts.ActivityBump),
// 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,
AutostartBlockDaysOfWeek: tpl.AutostartBlockDaysOfWeek,

View File

@ -673,8 +673,6 @@ func ConvertTemplate(dbTemplate database.Template) Template {
// Some of these fields are meant to be accessed using a specialized
// interface (for entitlement purposes), but for telemetry purposes
// there's minimal harm accessing them directly.
UseMaxTTL: dbTemplate.UseMaxTtl,
MaxTTLMillis: time.Duration(dbTemplate.MaxTTL).Milliseconds(),
DefaultTTLMillis: time.Duration(dbTemplate.DefaultTTL).Milliseconds(),
AllowUserCancelWorkspaceJobs: dbTemplate.AllowUserCancelWorkspaceJobs,
AllowUserAutostart: dbTemplate.AllowUserAutostart,
@ -919,8 +917,6 @@ type Template struct {
Name string `json:"name"`
Description bool `json:"description"`
UseMaxTTL bool `json:"use_max_ttl"`
MaxTTLMillis int64 `json:"max_ttl_ms"`
DefaultTTLMillis int64 `json:"default_ttl_ms"`
AllowUserCancelWorkspaceJobs bool `json:"allow_user_cancel_workspace_jobs"`
AllowUserAutostart bool `json:"allow_user_autostart"`

View File

@ -228,7 +228,6 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
var (
defaultTTL time.Duration
activityBump = time.Hour // default
maxTTL time.Duration
autostopRequirementDaysOfWeek []string
autostartRequirementDaysOfWeek []string
autostopRequirementWeeks int64
@ -273,12 +272,7 @@ 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 maxTTL < 0 {
validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."})
}
if maxTTL != 0 && defaultTTL > maxTTL {
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 len(autostopRequirementDaysOfWeek) > 0 {
autostopRequirementDaysOfWeekParsed, err = codersdk.WeekdaysToBitmap(autostopRequirementDaysOfWeek)
if err != nil {
@ -291,12 +285,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
validErrs = append(validErrs, codersdk.ValidationError{Field: "autostart_requirement.days_of_week", Detail: err.Error()})
}
}
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."})
}
@ -376,10 +365,8 @@ 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,
ActivityBump: activityBump,
MaxTTL: maxTTL,
// Some of these values are enterprise-only, but the
// TemplateScheduleStore will handle avoiding setting them if
// unlicensed.
@ -580,16 +567,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
if req.ActivityBumpMillis < 0 {
validErrs = append(validErrs, codersdk.ValidationError{Field: "activity_bump_ms", Detail: "Must be a positive integer."})
}
if req.MaxTTLMillis < 0 {
validErrs = append(validErrs, codersdk.ValidationError{Field: "max_ttl_ms", Detail: "Must be a positive integer."})
}
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),
@ -673,8 +651,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
req.AllowUserCancelWorkspaceJobs == template.AllowUserCancelWorkspaceJobs &&
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() &&
req.ActivityBumpMillis == time.Duration(template.ActivityBump).Milliseconds() &&
useMaxTTL == scheduleOpts.UseMaxTTL &&
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() &&
autostopRequirementDaysOfWeekParsed == scheduleOpts.AutostopRequirement.DaysOfWeek &&
autostartRequirementDaysOfWeekParsed == scheduleOpts.AutostartRequirement.DaysOfWeek &&
req.AutostopRequirement.Weeks == scheduleOpts.AutostopRequirement.Weeks &&
@ -746,15 +722,12 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
activityBump := time.Duration(req.ActivityBumpMillis) * time.Millisecond
maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond
failureTTL := time.Duration(req.FailureTTLMillis) * time.Millisecond
inactivityTTL := time.Duration(req.TimeTilDormantMillis) * time.Millisecond
timeTilDormantAutoDelete := time.Duration(req.TimeTilDormantAutoDeleteMillis) * time.Millisecond
if defaultTTL != time.Duration(template.DefaultTTL) ||
activityBump != time.Duration(template.ActivityBump) ||
useMaxTTL != scheduleOpts.UseMaxTTL ||
maxTTL != time.Duration(template.MaxTTL) ||
autostopRequirementDaysOfWeekParsed != scheduleOpts.AutostopRequirement.DaysOfWeek ||
autostartRequirementDaysOfWeekParsed != scheduleOpts.AutostartRequirement.DaysOfWeek ||
req.AutostopRequirement.Weeks != scheduleOpts.AutostopRequirement.Weeks ||
@ -771,8 +744,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
UserAutostopEnabled: req.AllowUserAutostop,
DefaultTTL: defaultTTL,
ActivityBump: activityBump,
UseMaxTTL: useMaxTTL,
MaxTTL: maxTTL,
AutostopRequirement: schedule.TemplateAutostopRequirement{
DaysOfWeek: autostopRequirementDaysOfWeekParsed,
Weeks: req.AutostopRequirement.Weeks,
@ -921,8 +892,6 @@ func (api *API) convertTemplate(
Icon: template.Icon,
DefaultTTLMillis: time.Duration(template.DefaultTTL).Milliseconds(),
ActivityBumpMillis: time.Duration(template.ActivityBump).Milliseconds(),
UseMaxTTL: template.UseMaxTtl,
MaxTTLMillis: time.Duration(template.MaxTTL).Milliseconds(),
CreatedByID: template.CreatedBy,
CreatedByName: template.CreatedByUsername,
AllowUserAutostart: template.AllowUserAutostart,

View File

@ -273,8 +273,6 @@ func TestPostTemplateByOrganization(t *testing.T) {
AllowUserAutostop: options.UserAutostopEnabled,
DefaultTTL: int64(options.DefaultTTL),
ActivityBump: int64(options.ActivityBump),
UseMaxTtl: options.UseMaxTTL,
MaxTTL: int64(options.MaxTTL),
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
FailureTTL: int64(options.FailureTTL),
@ -302,7 +300,6 @@ 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)
@ -326,8 +323,6 @@ func TestPostTemplateByOrganization(t *testing.T) {
AllowUserAutostop: options.UserAutostopEnabled,
DefaultTTL: int64(options.DefaultTTL),
ActivityBump: int64(options.ActivityBump),
UseMaxTtl: options.UseMaxTTL,
MaxTTL: int64(options.MaxTTL),
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
FailureTTL: int64(options.FailureTTL),
@ -360,13 +355,11 @@ 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)
})
@ -391,36 +384,10 @@ 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) {
@ -722,136 +689,6 @@ func TestPatchTemplateMeta(t *testing.T) {
assert.False(t, updated.Deprecated)
})
t.Run("MaxTTL", func(t *testing.T) {
t.Parallel()
const (
defaultTTL = 1 * time.Hour
maxTTL = 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, maxTTL, options.MaxTTL)
}
err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
ID: template.ID,
UpdatedAt: dbtime.Now(),
AllowUserAutostart: options.UserAutostartEnabled,
AllowUserAutostop: options.UserAutostopEnabled,
DefaultTTL: int64(options.DefaultTTL),
ActivityBump: int64(options.ActivityBump),
MaxTTL: int64(options.MaxTTL),
UseMaxTtl: options.UseMaxTTL,
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
FailureTTL: int64(options.FailureTTL),
TimeTilDormant: int64(options.TimeTilDormant),
TimeTilDormantAutoDelete: int64(options.TimeTilDormantAutoDelete),
})
if !assert.NoError(t, err) {
return database.Template{}, err
}
return db.GetTemplateByID(ctx, template.ID)
},
},
})
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.DefaultTTLMillis = ptr.Ref(24 * 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: maxTTL.Milliseconds(),
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
})
require.NoError(t, err)
require.EqualValues(t, 2, atomic.LoadInt64(&setCalled))
require.EqualValues(t, 0, got.DefaultTTLMillis)
require.Equal(t, maxTTL.Milliseconds(), got.MaxTTLMillis)
require.Empty(t, got.DeprecationMessage)
require.False(t, got.Deprecated)
})
t.Run("DefaultTTLBigger", 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.DefaultTTLMillis = ptr.Ref(24 * time.Hour.Milliseconds())
})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
_, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
Name: template.Name,
DisplayName: template.DisplayName,
Description: template.Description,
Icon: template.Icon,
DefaultTTLMillis: (maxTTL * 2).Milliseconds(),
MaxTTLMillis: maxTTL.Milliseconds(),
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
})
require.Error(t, err)
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
require.Len(t, sdkErr.Validations, 1)
require.Equal(t, "default_ttl_ms", sdkErr.Validations[0].Field)
require.Contains(t, sdkErr.Validations[0].Detail, "Must be less than or equal to max_ttl_ms")
})
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.DefaultTTLMillis = ptr.Ref(24 * 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: defaultTTL.Milliseconds(),
MaxTTLMillis: maxTTL.Milliseconds(),
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
})
require.NoError(t, err)
require.Equal(t, defaultTTL.Milliseconds(), got.DefaultTTLMillis)
require.Zero(t, got.MaxTTLMillis)
require.Empty(t, got.DeprecationMessage)
require.False(t, got.Deprecated)
})
})
t.Run("CleanupTTLs", func(t *testing.T) {
t.Parallel()
@ -1148,8 +985,6 @@ func TestPatchTemplateMeta(t *testing.T) {
AllowUserAutostop: options.UserAutostopEnabled,
DefaultTTL: int64(options.DefaultTTL),
ActivityBump: int64(options.ActivityBump),
UseMaxTtl: options.UseMaxTTL,
MaxTTL: int64(options.MaxTTL),
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
FailureTTL: int64(options.FailureTTL),
@ -1221,8 +1056,6 @@ func TestPatchTemplateMeta(t *testing.T) {
AllowUserAutostop: options.UserAutostopEnabled,
DefaultTTL: int64(options.DefaultTTL),
ActivityBump: int64(options.ActivityBump),
UseMaxTtl: options.UseMaxTTL,
MaxTTL: int64(options.MaxTTL),
AutostopRequirementDaysOfWeek: int16(options.AutostopRequirement.DaysOfWeek),
AutostopRequirementWeeks: options.AutostopRequirement.Weeks,
FailureTTL: int64(options.FailureTTL),
@ -1316,38 +1149,6 @@ 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) {

View File

@ -480,13 +480,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
return
}
maxTTL := templateSchedule.MaxTTL
if !templateSchedule.UseMaxTTL {
// If we're using autostop requirements, there isn't a max TTL.
maxTTL = 0
}
dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, templateSchedule.DefaultTTL, maxTTL)
dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, templateSchedule.DefaultTTL)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid Workspace Time to Shutdown.",
@ -857,16 +851,10 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
return codersdk.ValidationError{Field: "ttl_ms", Detail: "Custom autostop TTL is not allowed for workspaces using this template."}
}
maxTTL := templateSchedule.MaxTTL
if !templateSchedule.UseMaxTTL {
// If we're using autostop requirements, there isn't a max TTL.
maxTTL = 0
}
// don't override 0 ttl with template default here because it indicates
// disabled autostop
var validityErr error
dbTTL, validityErr = validWorkspaceTTLMillis(req.TTLMillis, 0, maxTTL)
dbTTL, validityErr = validWorkspaceTTLMillis(req.TTLMillis, 0)
if validityErr != nil {
return codersdk.ValidationError{Field: "ttl_ms", Detail: validityErr.Error()}
}
@ -1685,20 +1673,9 @@ func convertWorkspaceTTLMillis(i sql.NullInt64) *int64 {
return &millis
}
func validWorkspaceTTLMillis(millis *int64, templateDefault, templateMax time.Duration) (sql.NullInt64, error) {
if templateDefault == 0 && templateMax != 0 || (templateMax > 0 && templateDefault > templateMax) {
templateDefault = templateMax
}
func validWorkspaceTTLMillis(millis *int64, templateDefault time.Duration) (sql.NullInt64, error) {
if ptr.NilOrZero(millis) {
if templateDefault == 0 {
if templateMax > 0 {
return sql.NullInt64{
Int64: int64(templateMax),
Valid: true,
}, nil
}
return sql.NullInt64{}, nil
}
@ -1718,10 +1695,6 @@ func validWorkspaceTTLMillis(millis *int64, templateDefault, templateMax time.Du
return sql.NullInt64{}, errTTLMax
}
if templateMax > 0 && truncated > templateMax {
return sql.NullInt64{}, xerrors.Errorf("time until shutdown must be less than or equal to the template's maximum TTL %q", templateMax.String())
}
return sql.NullInt64{
Valid: true,
Int64: int64(truncated),

View File

@ -543,7 +543,7 @@ when required by your organization's security policy.`,
}
deploymentGroupUserQuietHoursSchedule = serpent.Group{
Name: "User Quiet Hours Schedule",
Description: "Allow users to set quiet hours schedules each day for workspaces to avoid workspaces stopping during the day due to template max TTL.",
Description: "Allow users to set quiet hours schedules each day for workspaces to avoid workspaces stopping during the day due to template scheduling.",
YAML: "userQuietHoursSchedule",
}
deploymentGroupDangerous = serpent.Group{

View File

@ -92,12 +92,8 @@ 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"`
// TODO(@dean): remove max_ttl once autostop_requirement is matured
// Only one of MaxTTLMillis or AutostopRequirement can be specified.
MaxTTLMillis *int64 `json:"max_ttl_ms,omitempty"`
// AutostopRequirement allows optionally specifying the autostop requirement
// for workspaces created from this template. This is an enterprise feature.
// Only one of MaxTTLMillis or AutostopRequirement can be specified.
AutostopRequirement *TemplateAutostopRequirement `json:"autostop_requirement,omitempty"`
// AutostartRequirement allows optionally specifying the autostart allowed days
// for workspaces created from this template. This is an enterprise feature.

View File

@ -32,11 +32,6 @@ type Template struct {
Icon string `json:"icon"`
DefaultTTLMillis int64 `json:"default_ttl_ms"`
ActivityBumpMillis int64 `json:"activity_bump_ms"`
// UseMaxTTL picks whether to use the deprecated max TTL for the template or
// the new autostop requirement.
UseMaxTTL bool `json:"use_max_ttl"`
// TODO(@dean): remove max_ttl once autostop_requirement is matured
MaxTTLMillis int64 `json:"max_ttl_ms"`
// AutostopRequirement and AutostartRequirement are enterprise features. Its
// value is only used if your license is entitled to use the advanced template
// scheduling feature.
@ -214,13 +209,9 @@ 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"`
// TODO(@dean): remove max_ttl once autostop_requirement is matured
// Only one of MaxTTLMillis or AutostopRequirement can be specified.
MaxTTLMillis int64 `json:"max_ttl_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.
// Only one of MaxTTLMillis or AutostopRequirement can be specified.
AutostopRequirement *TemplateAutostopRequirement `json:"autostop_requirement,omitempty"`
AutostartRequirement *TemplateAutostartRequirement `json:"autostart_requirement,omitempty"`
AllowUserAutostart bool `json:"allow_user_autostart,omitempty"`

View File

@ -160,7 +160,7 @@ type UpdateUserQuietHoursScheduleRequest struct {
// window is. Schedule must not be empty. For new users, the schedule is set
// to 2am in their browser or computer's timezone. The schedule denotes the
// beginning of a 4 hour window where the workspace is allowed to
// automatically stop or restart due to maintenance or template max TTL.
// automatically stop or restart due to maintenance or template schedule.
//
// The schedule must be daily with a single time, and should have a timezone
// specified via a CRON_TZ prefix (otherwise UTC will be used).

View File

@ -8,22 +8,22 @@ We track the following resources:
<!-- Code generated by 'make docs/admin/audit-logs.md'. DO NOT EDIT -->
| <b>Resource<b> | |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| APIKey<br><i>login, logout, register, create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>ip_address</td><td>false</td></tr><tr><td>last_used</td><td>true</td></tr><tr><td>lifetime_seconds</td><td>false</td></tr><tr><td>login_type</td><td>false</td></tr><tr><td>scope</td><td>false</td></tr><tr><td>token_name</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| AuditOAuthConvertState<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>from_login_type</td><td>true</td></tr><tr><td>to_login_type</td><td>true</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| Group<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>members</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>quota_allowance</td><td>true</td></tr><tr><td>source</td><td>false</td></tr></tbody></table> |
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| HealthSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>dismissed_healthchecks</td><td>true</td></tr><tr><td>id</td><td>false</td></tr></tbody></table> |
| License<br><i>create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>exp</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>jwt</td><td>false</td></tr><tr><td>uploaded_at</td><td>true</td></tr><tr><td>uuid</td><td>true</td></tr></tbody></table> |
| OAuth2ProviderApp<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>callback_url</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| OAuth2ProviderAppSecret<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>app_id</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>display_secret</td><td>false</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>secret_prefix</td><td>false</td></tr></tbody></table> |
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>activity_bump</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_port_sharing_level</td><td>true</td></tr><tr><td>max_ttl</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>use_max_ttl</td><td>true</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>theme_preference</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
| <b>Resource<b> | |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| APIKey<br><i>login, logout, register, create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>ip_address</td><td>false</td></tr><tr><td>last_used</td><td>true</td></tr><tr><td>lifetime_seconds</td><td>false</td></tr><tr><td>login_type</td><td>false</td></tr><tr><td>scope</td><td>false</td></tr><tr><td>token_name</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| AuditOAuthConvertState<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>from_login_type</td><td>true</td></tr><tr><td>to_login_type</td><td>true</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| Group<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>members</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>quota_allowance</td><td>true</td></tr><tr><td>source</td><td>false</td></tr></tbody></table> |
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| HealthSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>dismissed_healthchecks</td><td>true</td></tr><tr><td>id</td><td>false</td></tr></tbody></table> |
| License<br><i>create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>exp</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>jwt</td><td>false</td></tr><tr><td>uploaded_at</td><td>true</td></tr><tr><td>uuid</td><td>true</td></tr></tbody></table> |
| OAuth2ProviderApp<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>callback_url</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| OAuth2ProviderAppSecret<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>app_id</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>display_secret</td><td>false</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>secret_prefix</td><td>false</td></tr></tbody></table> |
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>activity_bump</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_port_sharing_level</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>theme_preference</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
<!-- End generated by 'make docs/admin/audit-logs.md'. -->

16
docs/api/schemas.md generated
View File

@ -1414,7 +1414,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
"dormant_ttl_ms": 0,
"failure_ttl_ms": 0,
"icon": "string",
"max_ttl_ms": 0,
"name": "string",
"require_active_version": true,
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1"
@ -1430,7 +1429,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `allow_user_autostop` | boolean | false | | Allow user autostop allows users to set a custom workspace TTL to use in place of the template's DefaultTTL field. By default this is true. If false, the DefaultTTL will always be used. This can only be disabled when using an enterprise license. |
| `allow_user_cancel_workspace_jobs` | boolean | false | | Allow users to cancel in-progress workspace jobs. \*bool as the default value is "true". |
| `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | Autostart requirement allows optionally specifying the autostart allowed days for workspaces created from this template. This is an enterprise feature. |
| `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement allows optionally specifying the autostop requirement for workspaces created from this template. This is an enterprise feature. Only one of MaxTTLMillis or AutostopRequirement can be specified. |
| `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement allows optionally specifying the autostop requirement for workspaces created from this template. This is an enterprise feature. |
| `default_ttl_ms` | integer | false | | Default ttl ms allows optionally specifying the default TTL for all workspaces created from this template. |
| `delete_ttl_ms` | integer | false | | Delete ttl ms allows optionally specifying the max lifetime before Coder permanently deletes dormant workspaces created from this template. |
| `description` | string | false | | Description is a description of what the template contains. It must be less than 128 bytes. |
@ -1439,7 +1438,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `dormant_ttl_ms` | integer | false | | Dormant ttl ms allows optionally specifying the max lifetime before Coder locks inactive workspaces created from this template. |
| `failure_ttl_ms` | integer | false | | Failure ttl ms allows optionally specifying the max lifetime before Coder stops all resources for failed workspaces created from this template. |
| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured Only one of MaxTTLMillis or AutostopRequirement can be specified. |
| `name` | string | true | | Name is the name of the template. |
| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. |
| `template_version_id` | string | true | | Template version ID is an in-progress or completed job to use as an initial version of the template. |
@ -5410,15 +5408,13 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"max_port_share_level": "owner",
"max_ttl_ms": 0,
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "terraform",
"require_active_version": true,
"time_til_dormant_autodelete_ms": 0,
"time_til_dormant_ms": 0,
"updated_at": "2019-08-24T14:15:22Z",
"use_max_ttl": true
"updated_at": "2019-08-24T14:15:22Z"
}
```
@ -5447,7 +5443,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `icon` | string | false | | |
| `id` | string | false | | |
| `max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | |
| `max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured |
| `name` | string | false | | |
| `organization_id` | string | false | | |
| `provisioner` | string | false | | |
@ -5455,7 +5450,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `time_til_dormant_autodelete_ms` | integer | false | | |
| `time_til_dormant_ms` | integer | false | | |
| `updated_at` | string | false | | |
| `use_max_ttl` | boolean | false | | Use max ttl picks whether to use the deprecated max TTL for the template or the new autostop requirement. |
#### Enumerated Values
@ -6266,9 +6260,9 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
### Properties
| Name | Type | Required | Restrictions | Description |
| ---------- | ------ | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schedule` | string | true | | Schedule is a cron expression that defines when the user's quiet hours window is. Schedule must not be empty. For new users, the schedule is set to 2am in their browser or computer's timezone. The schedule denotes the beginning of a 4 hour window where the workspace is allowed to automatically stop or restart due to maintenance or template max TTL. |
| Name | Type | Required | Restrictions | Description |
| ---------- | ------ | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schedule` | string | true | | Schedule is a cron expression that defines when the user's quiet hours window is. Schedule must not be empty. For new users, the schedule is set to 2am in their browser or computer's timezone. The schedule denotes the beginning of a 4 hour window where the workspace is allowed to automatically stop or restart due to maintenance or template schedule. |
The schedule must be daily with a single time, and should have a timezone specified via a CRON_TZ prefix (otherwise UTC will be used).
If the schedule is empty, the user will be updated to use the default schedule.|

23
docs/api/templates.md generated
View File

@ -61,15 +61,13 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"max_port_share_level": "owner",
"max_ttl_ms": 0,
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "terraform",
"require_active_version": true,
"time_til_dormant_autodelete_ms": 0,
"time_til_dormant_ms": 0,
"updated_at": "2019-08-24T14:15:22Z",
"use_max_ttl": true
"updated_at": "2019-08-24T14:15:22Z"
}
]
```
@ -115,7 +113,6 @@ Status Code **200**
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](schemas.md#codersdkworkspaceagentportsharelevel) | false | | |
| `» max_ttl_ms` | integer | false | | Max ttl ms remove max_ttl once autostop_requirement is matured |
| `» name` | string | false | | |
| `» organization_id` | string(uuid) | false | | |
| `» provisioner` | string | false | | |
@ -123,7 +120,6 @@ Status Code **200**
| `» time_til_dormant_autodelete_ms` | integer | false | | |
| `» time_til_dormant_ms` | integer | false | | |
| `» updated_at` | string(date-time) | false | | |
| `» use_max_ttl` | boolean | false | | Use max ttl picks whether to use the deprecated max TTL for the template or the new autostop requirement. |
#### Enumerated Values
@ -173,7 +169,6 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
"dormant_ttl_ms": 0,
"failure_ttl_ms": 0,
"icon": "string",
"max_ttl_ms": 0,
"name": "string",
"require_active_version": true,
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1"
@ -228,15 +223,13 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"max_port_share_level": "owner",
"max_ttl_ms": 0,
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "terraform",
"require_active_version": true,
"time_til_dormant_autodelete_ms": 0,
"time_til_dormant_ms": 0,
"updated_at": "2019-08-24T14:15:22Z",
"use_max_ttl": true
"updated_at": "2019-08-24T14:15:22Z"
}
```
@ -369,15 +362,13 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"max_port_share_level": "owner",
"max_ttl_ms": 0,
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "terraform",
"require_active_version": true,
"time_til_dormant_autodelete_ms": 0,
"time_til_dormant_ms": 0,
"updated_at": "2019-08-24T14:15:22Z",
"use_max_ttl": true
"updated_at": "2019-08-24T14:15:22Z"
}
```
@ -686,15 +677,13 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"max_port_share_level": "owner",
"max_ttl_ms": 0,
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "terraform",
"require_active_version": true,
"time_til_dormant_autodelete_ms": 0,
"time_til_dormant_ms": 0,
"updated_at": "2019-08-24T14:15:22Z",
"use_max_ttl": true
"updated_at": "2019-08-24T14:15:22Z"
}
```
@ -810,15 +799,13 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"max_port_share_level": "owner",
"max_ttl_ms": 0,
"name": "string",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"provisioner": "terraform",
"require_active_version": true,
"time_til_dormant_autodelete_ms": 0,
"time_til_dormant_ms": 0,
"updated_at": "2019-08-24T14:15:22Z",
"use_max_ttl": true
"updated_at": "2019-08-24T14:15:22Z"
}
```

View File

@ -88,14 +88,6 @@ Specify a duration workspaces may be inactive prior to being moved to the dorman
Specify a duration workspaces may be in the dormant state prior to being deleted. This licensed feature's default is 0h (off). Maps to "Dormancy Auto-Deletion" in the UI.
### --max-ttl
| | |
| ---- | --------------------- |
| Type | <code>duration</code> |
Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting. This is an enterprise-only feature.
### --require-active-version
| | |

View File

@ -68,14 +68,6 @@ 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.
### --max-ttl
| | |
| ---- | --------------------- |
| Type | <code>duration</code> |
Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting, regardless of user activity. This is an enterprise-only feature. Maps to "Max lifetime" in the UI.
### --autostart-requirement-weekdays
| | |
@ -84,6 +76,22 @@ Edit the template maximum time before shutdown - workspaces created from this te
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'.
### --autostop-requirement-weekdays
| | |
| ---- | ------------------------- |
| Type | <code>string-array</code> |
Edit the template autostop requirement weekdays - workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.
### --autostop-requirement-weeks
| | |
| ---- | ---------------- |
| Type | <code>int</code> |
Edit the template autostop requirement weeks - workspaces created from this template must be restarted on an n-weekly basis.
### --failure-ttl
| | |

View File

@ -70,8 +70,6 @@ var auditableResourcesTypes = map[any]map[string]Action{
"description": ActionTrack,
"icon": ActionTrack,
"default_ttl": ActionTrack,
"max_ttl": ActionTrack,
"use_max_ttl": ActionTrack,
"autostart_block_days_of_week": ActionTrack,
"autostop_requirement_days_of_week": ActionTrack,
"autostop_requirement_weeks": ActionTrack,

View File

@ -181,7 +181,6 @@ func TestTemplateEdit(t *testing.T) {
expectedDescription = "My description"
expectedIcon = "icon.pjg"
expectedDefaultTTLMillis = time.Hour.Milliseconds()
expectedMaxTTLMillis = (time.Hour * 24).Milliseconds()
expectedAllowAutostart = false
expectedAllowAutostop = false
expectedFailureTTLMillis = time.Minute.Milliseconds()
@ -204,7 +203,6 @@ func TestTemplateEdit(t *testing.T) {
assert.Equal(t, expectedDescription, tpl.Description)
assert.Equal(t, expectedIcon, tpl.Icon)
assert.Equal(t, expectedDefaultTTLMillis, tpl.DefaultTTLMillis)
assert.Equal(t, expectedMaxTTLMillis, tpl.MaxTTLMillis)
assert.Equal(t, expectedAllowAutostart, tpl.AllowUserAutostart)
assert.Equal(t, expectedAllowAutostop, tpl.AllowUserAutostop)
assert.Equal(t, expectedFailureTTLMillis, tpl.FailureTTLMillis)
@ -225,7 +223,6 @@ func TestTemplateEdit(t *testing.T) {
Description: expectedDescription,
Icon: expectedIcon,
DefaultTTLMillis: expectedDefaultTTLMillis,
MaxTTLMillis: expectedMaxTTLMillis,
AllowUserAutostop: expectedAllowAutostop,
AllowUserAutostart: expectedAllowAutostart,
FailureTTLMillis: expectedFailureTTLMillis,
@ -267,7 +264,6 @@ func TestTemplateEdit(t *testing.T) {
expectedAutostartDaysOfWeek = []string{"monday", "wednesday", "friday"}
expectedAutoStopDaysOfWeek = []string{"tuesday", "thursday"}
expectedAutoStopWeeks = 2
expectedMaxTTLMillis = 0
template, err = ownerClient.UpdateTemplateMeta(ctx, dbtemplate.ID, codersdk.UpdateTemplateMeta{
Name: expectedName,

View File

@ -468,7 +468,7 @@ telemetrywhen required by your organization's security policy.
USER QUIET HOURS SCHEDULE OPTIONS:
Allow users to set quiet hours schedules each day for workspaces to avoid
workspaces stopping during the day due to template max TTL.
workspaces stopping during the day due to template scheduling.
--allow-custom-quiet-hours bool, $CODER_ALLOW_CUSTOM_QUIET_HOURS (default: true)
Allow users to set their own quiet hours schedule for workspaces to

View File

@ -76,8 +76,6 @@ func (*EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.Sto
UserAutostopEnabled: tpl.AllowUserAutostop,
DefaultTTL: time.Duration(tpl.DefaultTTL),
ActivityBump: time.Duration(tpl.ActivityBump),
MaxTTL: time.Duration(tpl.MaxTTL),
UseMaxTTL: tpl.UseMaxTtl,
AutostopRequirement: agpl.TemplateAutostopRequirement{
DaysOfWeek: uint8(tpl.AutostopRequirementDaysOfWeek),
Weeks: tpl.AutostopRequirementWeeks,
@ -105,8 +103,6 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
if int64(opts.DefaultTTL) == tpl.DefaultTTL &&
int64(opts.ActivityBump) == tpl.ActivityBump &&
opts.UseMaxTTL != tpl.UseMaxTtl &&
int64(opts.MaxTTL) == tpl.MaxTTL &&
int16(opts.AutostopRequirement.DaysOfWeek) == tpl.AutostopRequirementDaysOfWeek &&
opts.AutostartRequirement.DaysOfWeek == tpl.AutostartAllowedDays() &&
opts.AutostopRequirement.Weeks == tpl.AutostopRequirementWeeks &&
@ -141,8 +137,6 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
AllowUserAutostop: opts.UserAutostopEnabled,
DefaultTTL: int64(opts.DefaultTTL),
ActivityBump: int64(opts.ActivityBump),
UseMaxTtl: opts.UseMaxTTL,
MaxTTL: int64(opts.MaxTTL),
AutostopRequirementDaysOfWeek: int16(opts.AutostopRequirement.DaysOfWeek),
AutostopRequirementWeeks: opts.AutostopRequirement.Weeks,
// Database stores the inverse of the allowed days of the week.

View File

@ -293,8 +293,6 @@ func TestTemplateUpdateBuildDeadlines(t *testing.T) {
UserAutostartEnabled: false,
UserAutostopEnabled: false,
DefaultTTL: 0,
MaxTTL: 0,
UseMaxTTL: false,
AutostopRequirement: autostopReq,
FailureTTL: 0,
TimeTilDormant: 0,
@ -571,8 +569,6 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) {
UserAutostartEnabled: false,
UserAutostopEnabled: false,
DefaultTTL: 0,
MaxTTL: 0,
UseMaxTTL: false,
AutostopRequirement: agplschedule.TemplateAutostopRequirement{
// Every day
DaysOfWeek: 0b01111111,

View File

@ -29,69 +29,6 @@ import (
func TestTemplates(t *testing.T) {
t.Parallel()
// TODO(@dean): remove legacy max_ttl tests
t.Run("CreateUpdateWorkspaceMaxTTL", func(t *testing.T) {
t.Parallel()
client, user := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAdvancedTemplateScheduling: 1,
},
},
})
anotherClient, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
exp := 24 * time.Hour.Milliseconds()
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.DefaultTTLMillis = &exp
ctr.MaxTTLMillis = &exp
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// No TTL provided should use template default
req := codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: "testing",
}
ws, err := anotherClient.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req)
require.NoError(t, err)
require.NotNil(t, ws.TTLMillis)
require.EqualValues(t, exp, *ws.TTLMillis)
// Editing a workspace to have a higher TTL than the template's max
// should error
exp = exp + time.Minute.Milliseconds()
err = anotherClient.UpdateWorkspaceTTL(ctx, ws.ID, codersdk.UpdateWorkspaceTTLRequest{
TTLMillis: &exp,
})
require.Error(t, err)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
require.Len(t, apiErr.Validations, 1)
require.Equal(t, apiErr.Validations[0].Field, "ttl_ms")
require.Contains(t, apiErr.Validations[0].Detail, "time until shutdown must be less than or equal to the template's maximum TTL")
// Creating workspace with TTL higher than max should error
req.Name = "testing2"
req.TTLMillis = &exp
ws, err = anotherClient.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req)
require.Error(t, err)
apiErr = nil
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
require.Len(t, apiErr.Validations, 1)
require.Equal(t, apiErr.Validations[0].Field, "ttl_ms")
require.Contains(t, apiErr.Validations[0].Detail, "time until shutdown must be less than or equal to the template's maximum TTL")
})
t.Run("Deprecated", func(t *testing.T) {
t.Parallel()
@ -249,60 +186,6 @@ func TestTemplates(t *testing.T) {
require.Empty(t, wpsr.Shares)
})
t.Run("BlockDisablingAutoOffWithMaxTTL", func(t *testing.T) {
t.Parallel()
client, user := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAdvancedTemplateScheduling: 1,
},
},
})
anotherClient, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
exp := 24 * time.Hour.Milliseconds()
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.MaxTTLMillis = &exp
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// No TTL provided should use template default
req := codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: "testing",
}
ws, err := anotherClient.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req)
require.NoError(t, err)
require.NotNil(t, ws.TTLMillis)
require.EqualValues(t, exp, *ws.TTLMillis)
// Editing a workspace to disable the TTL should do nothing
err = anotherClient.UpdateWorkspaceTTL(ctx, ws.ID, codersdk.UpdateWorkspaceTTLRequest{
TTLMillis: nil,
})
require.NoError(t, err)
ws, err = anotherClient.Workspace(ctx, ws.ID)
require.NoError(t, err)
require.EqualValues(t, exp, *ws.TTLMillis)
// Editing a workspace to have a TTL of 0 should do nothing
zero := int64(0)
err = anotherClient.UpdateWorkspaceTTL(ctx, ws.ID, codersdk.UpdateWorkspaceTTLRequest{
TTLMillis: &zero,
})
require.NoError(t, err)
ws, err = anotherClient.Workspace(ctx, ws.ID)
require.NoError(t, err)
require.EqualValues(t, exp, *ws.TTLMillis)
})
t.Run("SetAutostartRequirement", func(t *testing.T) {
t.Parallel()

View File

@ -239,7 +239,6 @@ export interface CreateTemplateRequest {
readonly template_version_id: string;
readonly default_ttl_ms?: number;
readonly activity_bump_ms?: number;
readonly max_ttl_ms?: number;
readonly autostop_requirement?: TemplateAutostopRequirement;
readonly autostart_requirement?: TemplateAutostartRequirement;
readonly allow_user_cancel_workspace_jobs?: boolean;
@ -1165,8 +1164,6 @@ export interface Template {
readonly icon: string;
readonly default_ttl_ms: number;
readonly activity_bump_ms: number;
readonly use_max_ttl: boolean;
readonly max_ttl_ms: number;
readonly autostop_requirement: TemplateAutostopRequirement;
readonly autostart_requirement: TemplateAutostartRequirement;
readonly created_by_id: string;
@ -1425,7 +1422,6 @@ export interface UpdateTemplateMeta {
readonly icon?: string;
readonly default_ttl_ms?: number;
readonly activity_bump_ms?: number;
readonly max_ttl_ms?: number;
readonly autostop_requirement?: TemplateAutostopRequirement;
readonly autostart_requirement?: TemplateAutostartRequirement;
readonly allow_user_autostart?: boolean;

View File

@ -41,8 +41,6 @@ export interface CreateTemplateData {
description: string;
icon: string;
default_ttl_hours: number;
use_max_ttl: boolean;
max_ttl_hours: number;
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
autostop_requirement_weeks: number;
@ -70,13 +68,6 @@ const defaultInitialValues: CreateTemplateData = {
description: "",
icon: "",
default_ttl_hours: 24,
// max_ttl is an enterprise-only feature, and the server ignores the value if
// you are not licensed. We hide the form value based on entitlements.
//
// The maximum value is 30 days but we default to 7 days as it's a much more
// sensible value for most teams.
use_max_ttl: false, // autostop_requirement is default
max_ttl_hours: 24 * 7,
// autostop_requirement is an enterprise-only feature, and the server ignores
// the value if you are not licensed. We hide the form value based on
// entitlements.
@ -110,7 +101,6 @@ const getInitialValues = ({
if (!allowAdvancedScheduling) {
initialValues = {
...initialValues,
max_ttl_hours: 0,
autostop_requirement_days_of_week: "off",
autostop_requirement_weeks: 1,
};

View File

@ -12,18 +12,14 @@ const provisioner: ProvisionerType =
typeof (window as any).playwright !== "undefined" ? "echo" : "terraform";
export const newTemplate = (formData: CreateTemplateData) => {
let {
max_ttl_hours,
autostop_requirement_days_of_week,
autostop_requirement_weeks,
} = formData;
const { autostop_requirement_days_of_week, autostop_requirement_weeks } =
formData;
const safeTemplateData = {
name: formData.name,
display_name: formData.display_name,
description: formData.description,
icon: formData.icon,
use_max_ttl: formData.use_max_ttl,
allow_user_autostart: formData.allow_user_autostart,
allow_user_autostop: formData.allow_user_autostop,
allow_user_cancel_workspace_jobs: formData.allow_user_cancel_workspace_jobs,
@ -31,18 +27,10 @@ export const newTemplate = (formData: CreateTemplateData) => {
allow_everyone_group_access: formData.allow_everyone_group_access,
};
if (formData.use_max_ttl) {
autostop_requirement_days_of_week = "off";
autostop_requirement_weeks = 1;
} else {
max_ttl_hours = 0;
}
return {
...safeTemplateData,
disable_everyone_group_access: !formData.allow_everyone_group_access,
default_ttl_ms: formData.default_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
max_ttl_ms: max_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
autostop_requirement: {
days_of_week: calculateAutostopRequirementDaysValue(
autostop_requirement_days_of_week,

View File

@ -14,7 +14,7 @@ import { TemplateSettingsPage } from "./TemplateSettingsPage";
type FormValues = Required<
Omit<
UpdateTemplateMeta,
"default_ttl_ms" | "activity_bump_ms" | "max_ttl_ms" | "deprecation_message"
"default_ttl_ms" | "activity_bump_ms" | "deprecation_message"
>
>;

View File

@ -46,26 +46,6 @@ export const ActivityBumpHelperText = (props: { bump?: number }) => {
);
};
export const MaxTTLHelperText = (props: { ttl?: number }) => {
const { ttl = 0 } = props;
// Error will show once field is considered touched
if (ttl < 0) {
return null;
}
if (ttl === 0) {
return <span>Workspaces may run indefinitely.</span>;
}
return (
<span>
Workspaces must stop within {ttl} {hours(ttl)} of starting, regardless of
any active connections.
</span>
);
};
export const FailureTTLHelperText = (props: { ttl?: number }) => {
const { ttl = 0 } = props;

View File

@ -1,7 +1,6 @@
import { useTheme } from "@emotion/react";
import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel";
import Link from "@mui/material/Link";
import MenuItem from "@mui/material/MenuItem";
import Switch from "@mui/material/Switch";
import TextField from "@mui/material/TextField";
@ -15,7 +14,6 @@ import {
FormFields,
} from "components/Form/Form";
import { Stack } from "components/Stack/Stack";
import { docs } from "utils/docs";
import { getFormHelpers } from "utils/formUtils";
import {
calculateAutostopRequirementDaysValue,
@ -38,7 +36,6 @@ import {
DormancyAutoDeletionTTLHelperText,
DormancyTTLHelperText,
FailureTTLHelperText,
MaxTTLHelperText,
} from "./TTLHelperText";
import {
useWorkspacesToGoDormant,
@ -77,15 +74,6 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
// on display, convert from ms => hours
default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION,
activity_bump_ms: template.activity_bump_ms / MS_HOUR_CONVERSION,
// the API ignores these values, but to avoid tripping up validation set
// it to zero if the user can't set the field.
use_max_ttl:
template.use_max_ttl === undefined
? template.max_ttl_ms > 0
: template.use_max_ttl,
max_ttl_ms: allowAdvancedScheduling
? template.max_ttl_ms / MS_HOUR_CONVERSION
: 0,
failure_ttl_ms: allowAdvancedScheduling
? template.failure_ttl_ms / MS_DAY_CONVERSION
: 0,
@ -214,10 +202,6 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
activity_bump_ms: form.values.activity_bump_ms
? form.values.activity_bump_ms * MS_HOUR_CONVERSION
: undefined,
max_ttl_ms:
form.values.max_ttl_ms && form.values.use_max_ttl
? form.values.max_ttl_ms * MS_HOUR_CONVERSION
: undefined,
failure_ttl_ms: form.values.failure_ttl_ms
? form.values.failure_ttl_ms * MS_DAY_CONVERSION
: undefined,
@ -228,14 +212,12 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
? form.values.time_til_dormant_autodelete_ms * MS_DAY_CONVERSION
: undefined,
autostop_requirement: form.values.use_max_ttl
? undefined
: {
days_of_week: calculateAutostopRequirementDaysValue(
form.values.autostop_requirement_days_of_week,
),
weeks: autostop_requirement_weeks,
},
autostop_requirement: {
days_of_week: calculateAutostopRequirementDaysValue(
form.values.autostop_requirement_days_of_week,
),
weeks: autostop_requirement_weeks,
},
autostart_requirement: {
days_of_week: form.values.autostart_requirement_days_of_week,
},
@ -330,27 +312,6 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
}
};
const handleToggleUseMaxTTL = async () => {
const val = !form.values.use_max_ttl;
if (val) {
// set max_ttl to 1, set autostop_requirement to empty
await form.setValues({
...form.values,
use_max_ttl: val,
max_ttl_ms: 1,
autostop_requirement_days_of_week: "off",
autostop_requirement_weeks: 1,
});
} else {
// set max_ttl to 0
await form.setValues({
...form.values,
use_max_ttl: val,
max_ttl_ms: 0,
});
}
};
return (
<HorizontalForm
onSubmit={form.handleSubmit}
@ -402,7 +363,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
/>
),
})}
disabled={isSubmitting || form.values.use_max_ttl}
disabled={isSubmitting}
fullWidth
select
value={form.values.autostop_requirement_days_of_week}
@ -433,7 +394,6 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
})}
disabled={
isSubmitting ||
form.values.use_max_ttl ||
!["saturday", "sunday"].includes(
form.values.autostop_requirement_days_of_week || "",
)
@ -446,68 +406,6 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
</Stack>
</FormSection>
<FormSection
title="Max Lifetime"
description="Define the maximum lifetime for workspaces created from this template."
deprecated
>
<Stack direction="column" spacing={4}>
<Stack direction="row" alignItems="center">
<FormControlLabel
control={
<Checkbox
id="use_max_ttl"
size="small"
disabled={isSubmitting || !allowAdvancedScheduling}
onChange={handleToggleUseMaxTTL}
name="use_max_ttl"
checked={form.values.use_max_ttl}
/>
}
label={
<Stack spacing={0.5}>
<strong>
Use a max lifetime instead of a required autostop schedule.
</strong>
<span
css={{
fontSize: 12,
color: theme.palette.text.secondary,
}}
>
Use a maximum lifetime for workspaces created from this
template instead of an autostop requirement as configured
above.
</span>
</Stack>
}
/>
</Stack>
<TextField
{...getFieldHelpers("max_ttl_ms", {
helperText: allowAdvancedScheduling ? (
<MaxTTLHelperText ttl={form.values.max_ttl_ms} />
) : (
<>
You need an enterprise license to use it{" "}
<Link href={docs("/enterprise")}>Learn more</Link>.
</>
),
})}
disabled={
isSubmitting ||
!form.values.use_max_ttl ||
!allowAdvancedScheduling
}
fullWidth
inputProps={{ min: 0, step: 1 }}
label="Max lifetime (hours)"
type="number"
/>
</Stack>
</FormSection>
<FormSection
title="Allow users scheduling"
description="Allow users to set custom autostart and autostop scheduling options for workspaces created from this template."

View File

@ -19,8 +19,6 @@ import TemplateSchedulePage from "./TemplateSchedulePage";
const validFormValues: TemplateScheduleFormValues = {
default_ttl_ms: 1,
activity_bump_ms: 1,
use_max_ttl: true,
max_ttl_ms: 2,
failure_ttl_ms: 7,
time_til_dormant_ms: 180,
time_til_dormant_autodelete_ms: 30,
@ -63,7 +61,6 @@ type FillAndSubmitConfig = {
const fillAndSubmitForm = async ({
default_ttl_ms,
max_ttl_ms,
failure_ttl_ms,
time_til_dormant_ms,
time_til_dormant_autodelete_ms,
@ -78,17 +75,6 @@ const fillAndSubmitForm = async ({
await user.type(defaultTtlField, default_ttl_ms.toString());
}
if (max_ttl_ms) {
const useMaxTtlCheckbox = screen.getByRole("checkbox", {
name: /Use a max lifetime/i,
});
const maxTtlField = await screen.findByLabelText("Max lifetime (hours)");
await user.click(useMaxTtlCheckbox);
await user.clear(maxTtlField);
await user.type(maxTtlField, max_ttl_ms.toString());
}
if (failure_ttl_ms) {
const failureTtlField = screen.getByRole("checkbox", {
name: /Failure Cleanup/i,
@ -158,7 +144,7 @@ describe("TemplateSchedulePage", () => {
);
});
test("default and max ttl is converted to and from hours", async () => {
test("default is converted to and from hours", async () => {
await renderTemplateSchedulePage();
jest.spyOn(API, "updateTemplateMeta").mockResolvedValueOnce({
@ -176,7 +162,6 @@ describe("TemplateSchedulePage", () => {
"test-template",
expect.objectContaining({
default_ttl_ms: (validFormValues.default_ttl_ms || 0) * 3600000,
max_ttl_ms: (validFormValues.max_ttl_ms || 0) * 3600000,
}),
);
});

View File

@ -10,7 +10,6 @@ export interface TemplateScheduleFormValues
UpdateTemplateMeta,
"autostop_requirement" | "autostart_requirement"
> {
use_max_ttl: boolean;
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
autostop_requirement_weeks: number;
@ -39,14 +38,6 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
24 * MAX_TTL_DAYS /* 30 days in hours */,
"Please enter an activity bump duration that is less than or equal to 720 hours (30 days).",
),
max_ttl_ms: Yup.number()
.integer()
.required()
.min(0, "Maximum time until autostop must not be less than 0.")
.max(
24 * MAX_TTL_DAYS /* 30 days in hours */,
"Please enter a limit that is less than or equal to 720 hours (30 days).",
),
failure_ttl_ms: Yup.number()
.integer()
.required()

View File

@ -475,8 +475,6 @@ 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,
use_max_ttl: false,
max_ttl_ms: 0,
autostop_requirement: {
days_of_week: ["sunday"],
weeks: 1,