From 4e46ecf8714776e35d37e18187857d560a76ace5 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 8 May 2023 17:09:14 +0000 Subject: [PATCH 1/4] added cleanup flags on template create --- cli/templatecreate.go | 49 +++++++++++++++++-- coderd/templates.go | 25 ++++++++-- docs/cli/templates_create.md | 18 +++++++ .../TemplateScheduleForm.tsx | 6 +-- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 823a3cd1e45a4..86385c08b4335 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "net/http" "os" "path/filepath" "strings" @@ -29,6 +30,8 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { variablesFile string variables []string defaultTTL time.Duration + failureTTL time.Duration + inactivityTTL time.Duration uploadFlags templateUploadFlags ) @@ -41,6 +44,28 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { r.InitClient(client), ), Handler: func(inv *clibase.Invocation) error { + if failureTTL != 0 || inactivityTTL != 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 --inactivityTTL are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") + } + entitlements, err := client.Entitlements(inv.Context()) + var sdkErr *codersdk.Error + if xerrors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound { + return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set --failure-ttl or --inactivityTTL") + } else if err != nil { + return xerrors.Errorf("get entitlements: %w", err) + } + + 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 or --inactivityTTL") + } + } + organization, err := CurrentOrganization(inv, client) if err != nil { return err @@ -95,10 +120,16 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { } } + fmt.Println("defaultTTL as passed in", defaultTTL) + fmt.Println("failureTTL as passed in", failureTTL) + fmt.Println("inactivityTTL as passed in", inactivityTTL) + createReq := codersdk.CreateTemplateRequest{ - Name: templateName, - VersionID: job.ID, - DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()), + Name: templateName, + VersionID: job.ID, + DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()), + FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()), + InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()), } _, err = client.CreateTemplate(inv.Context(), organization.ID, createReq) @@ -143,6 +174,18 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { Default: "24h", Value: clibase.DurationOf(&defaultTTL), }, + { + Flag: "failure-ttl", + Description: "Specify a failure TTL for workspaces created from this template. This licensed feature's default is 0h (off).", + Default: "0h", + Value: clibase.DurationOf(&failureTTL), + }, + { + Flag: "inactivity-ttl", + Description: "Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).", + Default: "0h", + Value: clibase.DurationOf(&inactivityTTL), + }, uploadFlags.option(), { Flag: "test.provisioner", diff --git a/coderd/templates.go b/coderd/templates.go index c66c5875217bf..12e62c9a4d059 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -214,8 +214,10 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } var ( - defaultTTL time.Duration - maxTTL time.Duration + defaultTTL time.Duration + maxTTL time.Duration + failureTTL time.Duration + inactivityTTL time.Duration ) if createTemplate.DefaultTTLMillis != nil { defaultTTL = time.Duration(*createTemplate.DefaultTTLMillis) * time.Millisecond @@ -223,6 +225,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque if createTemplate.MaxTTLMillis != nil { maxTTL = time.Duration(*createTemplate.MaxTTLMillis) * time.Millisecond } + if createTemplate.FailureTTLMillis != nil { + failureTTL = time.Duration(*createTemplate.FailureTTLMillis) * time.Millisecond + } + if createTemplate.InactivityTTLMillis != nil { + inactivityTTL = time.Duration(*createTemplate.InactivityTTLMillis) * time.Millisecond + } var validErrs []codersdk.ValidationError if defaultTTL < 0 { @@ -234,6 +242,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque 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 failureTTL < 0 { + validErrs = append(validErrs, codersdk.ValidationError{Field: "failure_ttl_ms", Detail: "Must be a positive integer."}) + } + if inactivityTTL < 0 { + validErrs = append(validErrs, codersdk.ValidationError{Field: "inactivity_ttl_ms", Detail: "Must be a positive integer."}) + } if len(validErrs) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid create template request.", @@ -279,7 +293,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque UserAutostartEnabled: allowUserAutostart, UserAutostopEnabled: allowUserAutostop, DefaultTTL: defaultTTL, - MaxTTL: maxTTL, + // Some of these values are enterprise-only, but the + // TemplateScheduleStore will handle avoiding setting them if + // unlicensed. + MaxTTL: maxTTL, + FailureTTL: failureTTL, + InactivityTTL: inactivityTTL, }) if err != nil { return xerrors.Errorf("set template schedule options: %s", err) diff --git a/docs/cli/templates_create.md b/docs/cli/templates_create.md index 772dfe6335dae..f64857e8c95a1 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. Specify the directory to create from, use '-' to read tar from stdin. +### --failure-ttl + +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | + +Specify a failure TTL for workspaces created from this template. This licensed feature's default is 0h (off). + +### --inactivity-ttl + +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | + +Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off). + ### --parameter-file | | | diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx index 5e15afad4a941..53936751008c2 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx @@ -65,7 +65,6 @@ export const getValidationSchema = (): Yup.AnyObjectSchema => i18next.t("maxTTLMaxError", { ns: "templateSettingsPage" }), ), failure_ttl_ms: Yup.number() - .integer() .min(0, "Failure cleanup days must not be less than 0.") .test( "positive-if-enabled", @@ -80,7 +79,6 @@ export const getValidationSchema = (): Yup.AnyObjectSchema => }, ), inactivity_ttl_ms: Yup.number() - .integer() .min(0, "Inactivity cleanup days must not be less than 0.") .test( "positive-if-enabled", @@ -349,7 +347,7 @@ export const TemplateScheduleForm: FC = ({ )} disabled={isSubmitting || !form.values.failure_cleanup_enabled} fullWidth - inputProps={{ min: 0, step: 1 }} + inputProps={{ min: 0, step: "any" }} label="Time until cleanup (days)" variant="outlined" type="number" @@ -385,7 +383,7 @@ export const TemplateScheduleForm: FC = ({ isSubmitting || !form.values.inactivity_cleanup_enabled } fullWidth - inputProps={{ min: 0, step: 1 }} + inputProps={{ min: 0, step: "any" }} label="Time until cleanup (days)" variant="outlined" type="number" From 393735e82a9596fc78b9e47df1acf9148cb84ad9 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 8 May 2023 17:17:46 +0000 Subject: [PATCH 2/4] added cleanup flags on template edit --- cli/templatecreate.go | 6 ++---- cli/templateedit.go | 34 +++++++++++++++++++++++++++++++--- docs/cli/templates_edit.md | 18 ++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 86385c08b4335..8b3cd75198ec6 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -45,6 +45,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { ), Handler: func(inv *clibase.Invocation) error { if failureTTL != 0 || inactivityTTL != 0 { + // This call can be removed when workspace_actions is no longer experimental experiments, exErr := client.Experiments(inv.Context()) if exErr != nil { return xerrors.Errorf("get experiments: %w", exErr) @@ -53,6 +54,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) { return xerrors.Errorf("--failure-ttl and --inactivityTTL are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") } + entitlements, err := client.Entitlements(inv.Context()) var sdkErr *codersdk.Error if xerrors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound { @@ -120,10 +122,6 @@ func (r *RootCmd) templateCreate() *clibase.Cmd { } } - fmt.Println("defaultTTL as passed in", defaultTTL) - fmt.Println("failureTTL as passed in", failureTTL) - fmt.Println("inactivityTTL as passed in", inactivityTTL) - createReq := codersdk.CreateTemplateRequest{ Name: templateName, VersionID: job.ID, diff --git a/cli/templateedit.go b/cli/templateedit.go index c4c6e3fd27615..97bc3e13271a3 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -20,6 +20,8 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { icon string defaultTTL time.Duration maxTTL time.Duration + failureTTL time.Duration + inactivityTTL time.Duration allowUserCancelWorkspaceJobs bool allowUserAutostart bool allowUserAutostop bool @@ -34,17 +36,29 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { ), Short: "Edit the metadata of a template by name.", Handler: func(inv *clibase.Invocation) error { - if maxTTL != 0 || !allowUserAutostart || !allowUserAutostop { + // This clause can be removed when workspace_actions is no longer experimental + if failureTTL != 0 || inactivityTTL != 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 --inactivityTTL are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.") + } + } + + if maxTTL != 0 || !allowUserAutostart || !allowUserAutostop || failureTTL != 0 || inactivityTTL != 0 { entitlements, err := client.Entitlements(inv.Context()) var sdkErr *codersdk.Error if xerrors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound { - return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set --max-ttl, --allow-user-autostart=false or --allow-user-autostop=false") + return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set --max-ttl, --failure-ttl, --inactivityTTL, --allow-user-autostart=false or --allow-user-autostop=false") } else if err != nil { return xerrors.Errorf("get entitlements: %w", err) } if !entitlements.Features[codersdk.FeatureAdvancedTemplateScheduling].Enabled { - return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --max-ttl, --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 --max-ttl, --failure-ttl, --inactivityTTL, --allow-user-autostart=false or --allow-user-autostop=false") } } @@ -65,6 +79,8 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { Icon: icon, DefaultTTLMillis: defaultTTL.Milliseconds(), MaxTTLMillis: maxTTL.Milliseconds(), + FailureTTLMillis: failureTTL.Milliseconds(), + InactivityTTLMillis: inactivityTTL.Milliseconds(), AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs, AllowUserAutostart: allowUserAutostart, AllowUserAutostop: allowUserAutostop, @@ -110,6 +126,18 @@ func (r *RootCmd) templateEdit() *clibase.Cmd { 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: clibase.DurationOf(&maxTTL), }, + { + Flag: "failure-ttl", + Description: "Specify a failure TTL for workspaces created from this template. This licensed feature's default is 0h (off).", + Default: "0h", + Value: clibase.DurationOf(&failureTTL), + }, + { + Flag: "inactivity-ttl", + Description: "Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).", + Default: "0h", + Value: clibase.DurationOf(&inactivityTTL), + }, { Flag: "allow-user-cancel-workspace-jobs", Description: "Allow users to cancel in-progress workspace jobs.", diff --git a/docs/cli/templates_edit.md b/docs/cli/templates_edit.md index 4752df59a0a71..2d25da15b7cc1 100644 --- a/docs/cli/templates_edit.md +++ b/docs/cli/templates_edit.md @@ -63,6 +63,15 @@ Edit the template description. Edit the template display name. +### --failure-ttl + +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | + +Specify a failure TTL for workspaces created from this template. This licensed feature's default is 0h (off). + ### --icon | | | @@ -71,6 +80,15 @@ Edit the template display name. Edit the template icon path. +### --inactivity-ttl + +| | | +| ------- | --------------------- | +| Type | duration | +| Default | 0h | + +Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off). + ### --max-ttl | | | From a5b996c6e85069216c304b96ce36b7197bafc2ec Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 8 May 2023 18:34:03 +0000 Subject: [PATCH 3/4] fixed tests --- cli/testdata/coder_templates_create_--help.golden | 8 ++++++++ cli/testdata/coder_templates_edit_--help.golden | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/cli/testdata/coder_templates_create_--help.golden b/cli/testdata/coder_templates_create_--help.golden index 9f4a82816ff58..473f95b0f21f5 100644 --- a/cli/testdata/coder_templates_create_--help.golden +++ b/cli/testdata/coder_templates_create_--help.golden @@ -9,6 +9,14 @@ Create a template from the current directory or as specified by flag -d, --directory string (default: .) Specify the directory to create from, use '-' to read tar from stdin. + --failure-ttl duration (default: 0h) + Specify a failure TTL for workspaces created from this template. This + licensed feature's default is 0h (off). + + --inactivity-ttl duration (default: 0h) + Specify an inactivity TTL for workspaces created from this template. + This licensed feature's default is 0h (off). + --parameter-file string Specify a file path with parameter values. diff --git a/cli/testdata/coder_templates_edit_--help.golden b/cli/testdata/coder_templates_edit_--help.golden index 271f0d9b9ed56..09c0b7209e78a 100644 --- a/cli/testdata/coder_templates_edit_--help.golden +++ b/cli/testdata/coder_templates_edit_--help.golden @@ -24,9 +24,17 @@ Edit the metadata of a template by name. --display-name string Edit the template display name. + --failure-ttl duration (default: 0h) + Specify a failure TTL for workspaces created from this template. This + licensed feature's default is 0h (off). + --icon string Edit the template icon path. + --inactivity-ttl duration (default: 0h) + Specify an inactivity TTL for workspaces created from this template. + This licensed feature's default is 0h (off). + --max-ttl duration Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after From 9e7791867a62f81afc1eada0a350285aa60908e5 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 8 May 2023 18:40:34 +0000 Subject: [PATCH 4/4] added to tests --- cli/templateedit_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli/templateedit_test.go b/cli/templateedit_test.go index eb84b0ba0f82a..384fc30e7e1ae 100644 --- a/cli/templateedit_test.go +++ b/cli/templateedit_test.go @@ -453,6 +453,8 @@ func TestTemplateEdit(t *testing.T) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { ctr.DefaultTTLMillis = nil ctr.MaxTTLMillis = nil + ctr.FailureTTLMillis = nil + ctr.InactivityTTLMillis = nil }) // Test the cli command with --allow-user-autostart. @@ -496,6 +498,8 @@ func TestTemplateEdit(t *testing.T) { assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis) assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart) assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop) + assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis) + assert.Equal(t, template.InactivityTTLMillis, updated.InactivityTTLMillis) }) t.Run("BlockedNotEntitled", func(t *testing.T) { @@ -582,6 +586,8 @@ func TestTemplateEdit(t *testing.T) { assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis) assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart) assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop) + assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis) + assert.Equal(t, template.InactivityTTLMillis, updated.InactivityTTLMillis) }) t.Run("Entitled", func(t *testing.T) { t.Parallel() @@ -672,6 +678,8 @@ func TestTemplateEdit(t *testing.T) { assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis) assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart) assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop) + assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis) + assert.Equal(t, template.InactivityTTLMillis, updated.InactivityTTLMillis) }) }) }