diff --git a/cli/templatecreate.go b/cli/templatecreate.go index da0793121d949..e6904d2a065f6 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -31,10 +31,11 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { disableEveryone bool requireActiveVersion bool - defaultTTL time.Duration - failureTTL time.Duration - inactivityTTL time.Duration - maxTTL time.Duration + defaultTTL time.Duration + failureTTL time.Duration + dormancyThreshold time.Duration + dormancyAutoDeletion time.Duration + maxTTL time.Duration uploadFlags templateUploadFlags ) @@ -47,10 +48,10 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { r.InitClient(client), ), Handler: func(inv *clibase.Invocation) error { - isTemplateSchedulingOptionsSet := failureTTL != 0 || inactivityTTL != 0 || maxTTL != 0 + isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 || maxTTL != 0 if isTemplateSchedulingOptionsSet || requireActiveVersion { - if failureTTL != 0 || inactivityTTL != 0 { + if failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 { // This call can be removed when workspace_actions is no longer experimental experiments, exErr := client.Experiments(inv.Context()) if exErr != nil { @@ -58,7 +59,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { } if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) { - return xerrors.Errorf("--failure-ttl and --inactivity-ttl are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") + return xerrors.Errorf("--failure-ttl, --dormancy-threshold, and --dormancy-auto-deletion are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") } } @@ -153,14 +154,15 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { } createReq := codersdk.CreateTemplateRequest{ - Name: templateName, - VersionID: job.ID, - DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()), - FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()), - MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()), - TimeTilDormantMillis: ptr.Ref(inactivityTTL.Milliseconds()), - DisableEveryoneGroupAccess: disableEveryone, - RequireActiveVersion: requireActiveVersion, + Name: templateName, + 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, + RequireActiveVersion: requireActiveVersion, } _, err = client.CreateTemplate(inv.Context(), organization.ID, createReq) @@ -220,11 +222,18 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { Value: clibase.DurationOf(&failureTTL), }, { - Flag: "inactivity-ttl", - Description: "Specify an inactivity TTL for workspaces created from this template. It is the amount of time the workspace is not used before it is be stopped and auto-locked. This includes across multiple builds (e.g. auto-starts and stops). This licensed feature's default is 0h (off). Maps to \"Dormancy threshold\" in the UI.", + Flag: "dormancy-threshold", + Description: "Specify a duration workspaces may be inactive prior to being moved to the dormant state. This licensed feature's default is 0h (off). Maps to \"Dormancy threshold\" in the UI.", Default: "0h", - Value: clibase.DurationOf(&inactivityTTL), + Value: clibase.DurationOf(&dormancyThreshold), }, + { + Flag: "dormancy-auto-deletion", + Description: "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.", + Default: "0h", + Value: clibase.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.", diff --git a/cli/templateedit.go b/cli/templateedit.go index 9c46e98427f8d..91d09486139d5 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -28,7 +28,8 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { autostopRequirementWeeks int64 autostartRequirementDaysOfWeek []string failureTTL time.Duration - inactivityTTL time.Duration + dormancyThreshold time.Duration + dormancyAutoDeletion time.Duration allowUserCancelWorkspaceJobs bool allowUserAutostart bool allowUserAutostop bool @@ -46,14 +47,14 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { Short: "Edit the metadata of a template by name.", Handler: func(inv *clibase.Invocation) error { // This clause can be removed when workspace_actions is no longer experimental - if failureTTL != 0 || inactivityTTL != 0 { + if failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 { experiments, exErr := client.Experiments(inv.Context()) if exErr != nil { return xerrors.Errorf("get experiments: %w", exErr) } if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) { - return xerrors.Errorf("--failure-ttl and --inactivity-ttl are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") + return xerrors.Errorf("--failure-ttl, --dormancy-threshold, and --dormancy-auto-deletion are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") } } @@ -64,7 +65,8 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { !allowUserAutostop || maxTTL != 0 || failureTTL != 0 || - inactivityTTL != 0 || + dormancyThreshold != 0 || + dormancyAutoDeletion != 0 || len(autostartRequirementDaysOfWeek) > 0 requiresEntitlement := requiresScheduling || requireActiveVersion @@ -119,6 +121,15 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { if unsetAutostopRequirementDaysOfWeek { autostopRequirementDaysOfWeek = []string{} } + if failureTTL == 0 { + failureTTL = time.Duration(template.FailureTTLMillis) * time.Millisecond + } + if dormancyThreshold == 0 { + dormancyThreshold = time.Duration(template.TimeTilDormantMillis) * time.Millisecond + } + if dormancyAutoDeletion == 0 { + dormancyAutoDeletion = time.Duration(template.TimeTilDormantAutoDeleteMillis) * time.Millisecond + } // Only pass explicitly set deprecated values since the empty string // removes the deprecated message. By default if we pass a nil, @@ -129,7 +140,6 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { deprecated = &deprecationMessage } - // NOTE: coderd will ignore empty fields. req := codersdk.UpdateTemplateMeta{ Name: name, DisplayName: displayName, @@ -144,13 +154,14 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { AutostartRequirement: &codersdk.TemplateAutostartRequirement{ DaysOfWeek: autostartRequirementDaysOfWeek, }, - FailureTTLMillis: failureTTL.Milliseconds(), - TimeTilDormantMillis: inactivityTTL.Milliseconds(), - AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs, - AllowUserAutostart: allowUserAutostart, - AllowUserAutostop: allowUserAutostop, - RequireActiveVersion: requireActiveVersion, - DeprecationMessage: deprecated, + FailureTTLMillis: failureTTL.Milliseconds(), + TimeTilDormantMillis: dormancyThreshold.Milliseconds(), + TimeTilDormantAutoDeleteMillis: dormancyAutoDeletion.Milliseconds(), + AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs, + AllowUserAutostart: allowUserAutostart, + AllowUserAutostop: allowUserAutostop, + RequireActiveVersion: requireActiveVersion, + DeprecationMessage: deprecated, } _, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req) @@ -246,10 +257,16 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { Value: clibase.DurationOf(&failureTTL), }, { - Flag: "inactivity-ttl", - Description: "Specify an inactivity TTL for workspaces created from this template. It is the amount of time the workspace is not used before it is be stopped and auto-locked. This includes across multiple builds (e.g. auto-starts and stops). This licensed feature's default is 0h (off). Maps to \"Dormancy threshold\" in the UI.", + Flag: "dormancy-threshold", + Description: "Specify a duration workspaces may be inactive prior to being moved to the dormant state. This licensed feature's default is 0h (off). Maps to \"Dormancy threshold\" in the UI.", + Default: "0h", + Value: clibase.DurationOf(&dormancyThreshold), + }, + { + Flag: "dormancy-auto-deletion", + Description: "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.", Default: "0h", - Value: clibase.DurationOf(&inactivityTTL), + Value: clibase.DurationOf(&dormancyAutoDeletion), }, { Flag: "allow-user-cancel-workspace-jobs", diff --git a/cli/testdata/coder_templates_create_--help.golden b/cli/testdata/coder_templates_create_--help.golden index f458d3954dd62..ea896d944288b 100644 --- a/cli/testdata/coder_templates_create_--help.golden +++ b/cli/testdata/coder_templates_create_--help.golden @@ -14,6 +14,16 @@ OPTIONS: -d, --directory string (default: .) Specify the directory to create from, use '-' to read tar from stdin. + --dormancy-auto-deletion duration (default: 0h) + 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. + + --dormancy-threshold duration (default: 0h) + Specify a duration workspaces may be inactive prior to being moved to + the dormant state. This licensed feature's default is 0h (off). Maps + to "Dormancy threshold" in the UI. + --failure-ttl duration (default: 0h) Specify a failure TTL for workspaces created from this template. It is the amount of time after a failed "start" build before coder @@ -24,13 +34,6 @@ OPTIONS: Ignore warnings about not having a .terraform.lock.hcl file present in the template. - --inactivity-ttl duration (default: 0h) - Specify an inactivity TTL for workspaces created from this template. - It is the amount of time the workspace is not used before it is be - stopped and auto-locked. This includes across multiple builds (e.g. - auto-starts and stops). This licensed feature's default is 0h (off). - Maps to "Dormancy threshold" in the UI. - --max-ttl duration Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after diff --git a/cli/testdata/coder_templates_edit_--help.golden b/cli/testdata/coder_templates_edit_--help.golden index bd4cdee8d8482..94fa1ac45276c 100644 --- a/cli/testdata/coder_templates_edit_--help.golden +++ b/cli/testdata/coder_templates_edit_--help.golden @@ -38,6 +38,16 @@ OPTIONS: --display-name string Edit the template display name. + --dormancy-auto-deletion duration (default: 0h) + 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. + + --dormancy-threshold duration (default: 0h) + Specify a duration workspaces may be inactive prior to being moved to + the dormant state. This licensed feature's default is 0h (off). Maps + to "Dormancy threshold" in the UI. + --failure-ttl duration (default: 0h) Specify a failure TTL for workspaces created from this template. It is the amount of time after a failed "start" build before coder @@ -47,13 +57,6 @@ OPTIONS: --icon string Edit the template icon path. - --inactivity-ttl duration (default: 0h) - Specify an inactivity TTL for workspaces created from this template. - It is the amount of time the workspace is not used before it is be - stopped and auto-locked. This includes across multiple builds (e.g. - auto-starts and stops). This licensed feature's default is 0h (off). - Maps to "Dormancy threshold" in the UI. - --max-ttl duration Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after diff --git a/docs/cli/templates_create.md b/docs/cli/templates_create.md index 83c3b3c5b9aff..9535e2f12e6da 100644 --- a/docs/cli/templates_create.md +++ b/docs/cli/templates_create.md @@ -30,6 +30,24 @@ Specify a default TTL for workspaces created from this template. It is the defau Specify the directory to create from, use '-' to read tar from stdin. +### --dormancy-auto-deletion + +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | + +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. + +### --dormancy-threshold + +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | + +Specify a duration workspaces may be inactive prior to being moved to the dormant state. This licensed feature's default is 0h (off). Maps to "Dormancy threshold" in the UI. + ### --failure-ttl | | | @@ -48,15 +66,6 @@ Specify a failure TTL for workspaces created from this template. It is the amoun Ignore warnings about not having a .terraform.lock.hcl file present in the template. -### --inactivity-ttl - -| | | -| ------- | --------------------- | -| Type | duration | -| Default | 0h | - -Specify an inactivity TTL for workspaces created from this template. It is the amount of time the workspace is not used before it is be stopped and auto-locked. This includes across multiple builds (e.g. auto-starts and stops). This licensed feature's default is 0h (off). Maps to "Dormancy threshold" in the UI. - ### --max-ttl | | | diff --git a/docs/cli/templates_edit.md b/docs/cli/templates_edit.md index 0157b06404720..12577cbcaba23 100644 --- a/docs/cli/templates_edit.md +++ b/docs/cli/templates_edit.md @@ -79,31 +79,40 @@ Edit the template description. Edit the template display name. -### --failure-ttl +### --dormancy-auto-deletion | | | | ------- | --------------------- | | Type | duration | | Default | 0h | -Specify a failure TTL for workspaces created from this template. It is the amount of time after a failed "start" build before coder automatically schedules a "stop" build to cleanup.This licensed feature's default is 0h (off). Maps to "Failure cleanup" in the UI. +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. -### --icon +### --dormancy-threshold -| | | -| ---- | ------------------- | -| Type | string | +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | -Edit the template icon path. +Specify a duration workspaces may be inactive prior to being moved to the dormant state. This licensed feature's default is 0h (off). Maps to "Dormancy threshold" in the UI. -### --inactivity-ttl +### --failure-ttl | | | | ------- | --------------------- | | Type | duration | | Default | 0h | -Specify an inactivity TTL for workspaces created from this template. It is the amount of time the workspace is not used before it is be stopped and auto-locked. This includes across multiple builds (e.g. auto-starts and stops). This licensed feature's default is 0h (off). Maps to "Dormancy threshold" in the UI. +Specify a failure TTL for workspaces created from this template. It is the amount of time after a failed "start" build before coder automatically schedules a "stop" build to cleanup.This licensed feature's default is 0h (off). Maps to "Failure cleanup" in the UI. + +### --icon + +| | | +| ---- | ------------------- | +| Type | string | + +Edit the template icon path. ### --max-ttl diff --git a/enterprise/cli/templatecreate_test.go b/enterprise/cli/templatecreate_test.go index 8bbc84aba7b81..c95298f903be3 100644 --- a/enterprise/cli/templatecreate_test.go +++ b/enterprise/cli/templatecreate_test.go @@ -2,6 +2,7 @@ package cli_test import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -19,7 +20,7 @@ import ( func TestTemplateCreate(t *testing.T) { t.Parallel() - t.Run("OK", func(t *testing.T) { + t.Run("RequireActiveVersion", func(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) @@ -64,6 +65,62 @@ func TestTemplateCreate(t *testing.T) { require.True(t, template.RequireActiveVersion) }) + t.Run("WorkspaceCleanup", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{ + string(codersdk.ExperimentWorkspaceActions), + } + + client, user := coderdenttest.New(t, &coderdenttest.Options{ + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAdvancedTemplateScheduling: 1, + }, + }, + Options: &coderdtest.Options{ + DeploymentValues: dv, + IncludeProvisionerDaemon: true, + }, + }) + templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleTemplateAdmin()) + + source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: echo.ApplyComplete, + }) + + const ( + expectedFailureTTL = time.Hour * 3 + expectedDormancyThreshold = time.Hour * 4 + expectedDormancyAutoDeletion = time.Minute * 10 + ) + + inv, conf := newCLI(t, "templates", + "create", "new", + "--directory", source, + "--test.provisioner", string(database.ProvisionerTypeEcho), + "--failure-ttl="+expectedFailureTTL.String(), + "--dormancy-threshold="+expectedDormancyThreshold.String(), + "--dormancy-auto-deletion="+expectedDormancyAutoDeletion.String(), + "-y", + "--", + ) + + clitest.SetupConfig(t, templateAdmin, conf) + + err := inv.Run() + require.NoError(t, err) + + ctx := testutil.Context(t, testutil.WaitMedium) + template, err := templateAdmin.TemplateByName(ctx, user.OrganizationID, "new") + require.NoError(t, err) + require.Equal(t, expectedFailureTTL.Milliseconds(), template.FailureTTLMillis) + require.Equal(t, expectedDormancyThreshold.Milliseconds(), template.TimeTilDormantMillis) + require.Equal(t, expectedDormancyAutoDeletion.Milliseconds(), template.TimeTilDormantAutoDeleteMillis) + }) + t.Run("NotEntitled", func(t *testing.T) { t.Parallel() diff --git a/enterprise/cli/templateedit_test.go b/enterprise/cli/templateedit_test.go index c1b069e938e4c..ceeeb2aceb629 100644 --- a/enterprise/cli/templateedit_test.go +++ b/enterprise/cli/templateedit_test.go @@ -2,6 +2,7 @@ package cli_test import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -96,4 +97,75 @@ func TestTemplateEdit(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "your license is not entitled to use enterprise access control, so you cannot set --require-active-version") }) + + t.Run("WorkspaceCleanup", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{ + string(codersdk.ExperimentWorkspaceActions), + } + + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAdvancedTemplateScheduling: 1, + }, + }, + Options: &coderdtest.Options{ + DeploymentValues: dv, + IncludeProvisionerDaemon: true, + }, + }) + + templateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + version := coderdtest.CreateTemplateVersion(t, templateAdmin, owner.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, version.ID) + template := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID) + require.False(t, template.RequireActiveVersion) + + const ( + expectedFailureTTL = time.Hour * 3 + expectedDormancyThreshold = time.Hour * 4 + expectedDormancyAutoDeletion = time.Minute * 10 + ) + inv, conf := newCLI(t, "templates", + "edit", template.Name, + "--failure-ttl="+expectedFailureTTL.String(), + "--dormancy-threshold="+expectedDormancyThreshold.String(), + "--dormancy-auto-deletion="+expectedDormancyAutoDeletion.String(), + "-y", + ) + + clitest.SetupConfig(t, templateAdmin, conf) + + err := inv.Run() + require.NoError(t, err) + + ctx := testutil.Context(t, testutil.WaitMedium) + template, err = templateAdmin.Template(ctx, template.ID) + require.NoError(t, err) + require.Equal(t, expectedFailureTTL.Milliseconds(), template.FailureTTLMillis) + require.Equal(t, expectedDormancyThreshold.Milliseconds(), template.TimeTilDormantMillis) + require.Equal(t, expectedDormancyAutoDeletion.Milliseconds(), template.TimeTilDormantAutoDeleteMillis) + + inv, conf = newCLI(t, "templates", + "edit", template.Name, + "--display-name=idc", + "-y", + ) + + clitest.SetupConfig(t, templateAdmin, conf) + + err = inv.Run() + require.NoError(t, err) + + // Refetch the template to assert we haven't inadvertently updated + // the values to their default values. + template, err = templateAdmin.Template(ctx, template.ID) + require.NoError(t, err) + require.Equal(t, expectedFailureTTL.Milliseconds(), template.FailureTTLMillis) + require.Equal(t, expectedDormancyThreshold.Milliseconds(), template.TimeTilDormantMillis) + require.Equal(t, expectedDormancyAutoDeletion.Milliseconds(), template.TimeTilDormantAutoDeleteMillis) + }) }