Skip to content

Commit b202076

Browse files
authored
feat: add default autostart and ttl for new workspaces (#1632)
* database: add autostart_schedule and ttl to InsertWorkspace; make gen * coderd: workspaces: consume additional fields of CreateWorkspaceRequest * cli: update: add support for TTL and autostart_schedule * cli: create: add unit tests * coder: import `time/tzdata` for embedded timezone database * autobuild: fix unit test that only runs with a real db
1 parent c465f8a commit b202076

File tree

14 files changed

+240
-129
lines changed

14 files changed

+240
-129
lines changed

cli/create.go

+33-6
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ import (
1010

1111
"github.com/coder/coder/cli/cliflag"
1212
"github.com/coder/coder/cli/cliui"
13+
"github.com/coder/coder/coderd/autobuild/schedule"
1314
"github.com/coder/coder/codersdk"
1415
)
1516

1617
func create() *cobra.Command {
1718
var (
18-
workspaceName string
19-
templateName string
20-
parameterFile string
19+
autostartMinute string
20+
autostartHour string
21+
autostartDow string
22+
parameterFile string
23+
templateName string
24+
ttl time.Duration
25+
tzName string
26+
workspaceName string
2127
)
2228
cmd := &cobra.Command{
2329
Annotations: workspaceCommand,
@@ -54,6 +60,20 @@ func create() *cobra.Command {
5460
}
5561
}
5662

63+
tz, err := time.LoadLocation(tzName)
64+
if err != nil {
65+
return xerrors.Errorf("Invalid workspace autostart timezone: %w", err)
66+
}
67+
schedSpec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", tz.String(), autostartMinute, autostartHour, autostartDow)
68+
_, err = schedule.Weekly(schedSpec)
69+
if err != nil {
70+
return xerrors.Errorf("invalid workspace autostart schedule: %w", err)
71+
}
72+
73+
if ttl == 0 {
74+
return xerrors.Errorf("TTL must be at least 1 minute")
75+
}
76+
5777
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, workspaceName)
5878
if err == nil {
5979
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
@@ -174,9 +194,11 @@ func create() *cobra.Command {
174194

175195
before := time.Now()
176196
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.CreateWorkspaceRequest{
177-
TemplateID: template.ID,
178-
Name: workspaceName,
179-
ParameterValues: parameters,
197+
TemplateID: template.ID,
198+
Name: workspaceName,
199+
AutostartSchedule: &schedSpec,
200+
TTL: &ttl,
201+
ParameterValues: parameters,
180202
})
181203
if err != nil {
182204
return err
@@ -207,5 +229,10 @@ func create() *cobra.Command {
207229
cliui.AllowSkipPrompt(cmd)
208230
cliflag.StringVarP(cmd.Flags(), &templateName, "template", "t", "CODER_TEMPLATE_NAME", "", "Specify a template name.")
209231
cliflag.StringVarP(cmd.Flags(), &parameterFile, "parameter-file", "", "CODER_PARAMETER_FILE", "", "Specify a file path with parameter values.")
232+
cliflag.StringVarP(cmd.Flags(), &autostartMinute, "autostart-minute", "", "CODER_WORKSPACE_AUTOSTART_MINUTE", "0", "Specify the minute(s) at which the workspace should autostart (e.g. 0).")
233+
cliflag.StringVarP(cmd.Flags(), &autostartHour, "autostart-hour", "", "CODER_WORKSPACE_AUTOSTART_HOUR", "9", "Specify the hour(s) at which the workspace should autostart (e.g. 9).")
234+
cliflag.StringVarP(cmd.Flags(), &autostartDow, "autostart-day-of-week", "", "CODER_WORKSPACE_AUTOSTART_DOW", "MON-FRI", "Specify the days(s) on which the workspace should autostart (e.g. MON,TUE,WED,THU,FRI)")
235+
cliflag.StringVarP(cmd.Flags(), &tzName, "tz", "", "TZ", "", "Specify your timezone location for workspace autostart (e.g. US/Central).")
236+
cliflag.DurationVarP(cmd.Flags(), &ttl, "ttl", "", "CODER_WORKSPACE_TTL", 8*time.Hour, "Specify a time-to-live (TTL) for the workspace (e.g. 8h).")
210237
return cmd
211238
}

cli/create_test.go

+66-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88
"time"
99

10+
"github.com/stretchr/testify/assert"
1011
"github.com/stretchr/testify/require"
1112

1213
"github.com/coder/coder/cli/clitest"
@@ -25,7 +26,17 @@ func TestCreate(t *testing.T) {
2526
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
2627
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
2728
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
28-
cmd, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
29+
args := []string{
30+
"create",
31+
"my-workspace",
32+
"--template", template.Name,
33+
"--tz", "US/Central",
34+
"--autostart-minute", "0",
35+
"--autostart-hour", "*/2",
36+
"--autostart-day-of-week", "MON-FRI",
37+
"--ttl", "8h",
38+
}
39+
cmd, root := clitest.New(t, args...)
2940
clitest.SetupConfig(t, client, root)
3041
doneChan := make(chan struct{})
3142
pty := ptytest.New(t)
@@ -48,6 +59,60 @@ func TestCreate(t *testing.T) {
4859
<-doneChan
4960
})
5061

62+
t.Run("CreateErrInvalidTz", func(t *testing.T) {
63+
t.Parallel()
64+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
65+
user := coderdtest.CreateFirstUser(t, client)
66+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
67+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
68+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
69+
args := []string{
70+
"create",
71+
"my-workspace",
72+
"--template", template.Name,
73+
"--tz", "invalid",
74+
}
75+
cmd, root := clitest.New(t, args...)
76+
clitest.SetupConfig(t, client, root)
77+
doneChan := make(chan struct{})
78+
pty := ptytest.New(t)
79+
cmd.SetIn(pty.Input())
80+
cmd.SetOut(pty.Output())
81+
go func() {
82+
defer close(doneChan)
83+
err := cmd.Execute()
84+
assert.EqualError(t, err, "Invalid workspace autostart timezone: unknown time zone invalid")
85+
}()
86+
<-doneChan
87+
})
88+
89+
t.Run("CreateErrInvalidTTL", func(t *testing.T) {
90+
t.Parallel()
91+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
92+
user := coderdtest.CreateFirstUser(t, client)
93+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
94+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
95+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
96+
args := []string{
97+
"create",
98+
"my-workspace",
99+
"--template", template.Name,
100+
"--ttl", "0s",
101+
}
102+
cmd, root := clitest.New(t, args...)
103+
clitest.SetupConfig(t, client, root)
104+
doneChan := make(chan struct{})
105+
pty := ptytest.New(t)
106+
cmd.SetIn(pty.Input())
107+
cmd.SetOut(pty.Output())
108+
go func() {
109+
defer close(doneChan)
110+
err := cmd.Execute()
111+
assert.EqualError(t, err, "TTL must be at least 1 minute")
112+
}()
113+
<-doneChan
114+
})
115+
51116
t.Run("CreateFromListWithSkip", func(t *testing.T) {
52117
t.Parallel()
53118
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})

cmd/coder/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os/exec"
88
"path/filepath"
99
"strings"
10+
_ "time/tzdata"
1011

1112
"github.com/coder/coder/cli"
1213
"github.com/coder/coder/cli/cliui"

0 commit comments

Comments
 (0)