Skip to content

feat: add workspace actions cleanup configuration flags to CLI template create and edit #7453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions cli/templatecreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
Expand All @@ -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
)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down
34 changes: 31 additions & 3 deletions cli/templateedit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
}
}

Expand All @@ -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,
Expand Down Expand Up @@ -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.",
Expand Down
8 changes: 8 additions & 0 deletions cli/templateedit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
})
})
}
8 changes: 8 additions & 0 deletions cli/testdata/coder_templates_create_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 8 additions & 0 deletions cli/testdata/coder_templates_edit_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 22 additions & 3 deletions coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,23 @@ 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
}
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 {
Expand All @@ -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.",
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions docs/cli/templates_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

| | |
Expand Down
18 changes: 18 additions & 0 deletions docs/cli/templates_edit.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

| | |
Expand All @@ -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

| | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -349,7 +347,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)"
variant="outlined"
type="number"
Expand Down Expand Up @@ -385,7 +383,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)"
variant="outlined"
type="number"
Expand Down