mirror of https://github.com/coder/coder.git
feat: add workspace actions cleanup configuration flags to CLI template create and edit (#7453)
* added cleanup flags on template create * added cleanup flags on template edit * fixed tests * added to tests
This commit is contained in:
parent
816c37dd0d
commit
a42a36a474
|
@ -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,30 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||
r.InitClient(client),
|
||||
),
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -96,9 +123,11 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||
}
|
||||
|
||||
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 +172,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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 | <code>duration</code> |
|
||||
| Default | <code>0h</code> |
|
||||
|
||||
Specify a failure TTL for workspaces created from this template. This licensed feature's default is 0h (off).
|
||||
|
||||
### --inactivity-ttl
|
||||
|
||||
| | |
|
||||
| ------- | --------------------- |
|
||||
| Type | <code>duration</code> |
|
||||
| Default | <code>0h</code> |
|
||||
|
||||
Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).
|
||||
|
||||
### --parameter-file
|
||||
|
||||
| | |
|
||||
|
|
|
@ -63,6 +63,15 @@ Edit the template description.
|
|||
|
||||
Edit the template display name.
|
||||
|
||||
### --failure-ttl
|
||||
|
||||
| | |
|
||||
| ------- | --------------------- |
|
||||
| Type | <code>duration</code> |
|
||||
| Default | <code>0h</code> |
|
||||
|
||||
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 | <code>duration</code> |
|
||||
| Default | <code>0h</code> |
|
||||
|
||||
Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).
|
||||
|
||||
### --max-ttl
|
||||
|
||||
| | |
|
||||
|
|
|
@ -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",
|
||||
|
@ -344,7 +342,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
)}
|
||||
disabled={isSubmitting || !form.values.failure_cleanup_enabled}
|
||||
fullWidth
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
inputProps={{ min: 0, step: "any" }}
|
||||
label="Time until cleanup (days)"
|
||||
type="number"
|
||||
aria-label="Failure Cleanup"
|
||||
|
@ -378,7 +376,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
isSubmitting || !form.values.inactivity_cleanup_enabled
|
||||
}
|
||||
fullWidth
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
inputProps={{ min: 0, step: "any" }}
|
||||
label="Time until cleanup (days)"
|
||||
type="number"
|
||||
aria-label="Inactivity Cleanup"
|
||||
|
|
Loading…
Reference in New Issue