Skip to content

Commit 1455603

Browse files
authored
fix: cli: create: use new autostart format, opt-in by default (coder#2472)
1 parent edd1083 commit 1455603

File tree

2 files changed

+33
-152
lines changed

2 files changed

+33
-152
lines changed

cli/create.go

Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,17 @@ import (
1010

1111
"github.com/coder/coder/cli/cliflag"
1212
"github.com/coder/coder/cli/cliui"
13-
"github.com/coder/coder/coderd/autobuild/schedule"
1413
"github.com/coder/coder/coderd/util/ptr"
1514
"github.com/coder/coder/codersdk"
1615
)
1716

1817
func create() *cobra.Command {
1918
var (
20-
autostartMinute string
21-
autostartHour string
22-
autostartDow string
23-
parameterFile string
24-
templateName string
25-
ttl time.Duration
26-
tzName string
27-
workspaceName string
19+
parameterFile string
20+
templateName string
21+
startAt string
22+
stopAfter time.Duration
23+
workspaceName string
2824
)
2925
cmd := &cobra.Command{
3026
Annotations: workspaceCommand,
@@ -115,21 +111,13 @@ func create() *cobra.Command {
115111
}
116112
}
117113

118-
schedSpec, err := validSchedule(
119-
autostartMinute,
120-
autostartHour,
121-
autostartDow,
122-
tzName,
123-
time.Duration(template.MinAutostartIntervalMillis)*time.Millisecond,
124-
)
125-
if err != nil {
126-
return xerrors.Errorf("Invalid autostart schedule: %w", err)
127-
}
128-
if ttl < time.Minute {
129-
return xerrors.Errorf("TTL must be at least 1 minute")
130-
}
131-
if ttlMax := time.Duration(template.MaxTTLMillis) * time.Millisecond; ttl > ttlMax {
132-
return xerrors.Errorf("TTL must be below template maximum %s", ttlMax)
114+
var schedSpec *string
115+
if startAt != "" {
116+
sched, err := parseCLISchedule(startAt)
117+
if err != nil {
118+
return err
119+
}
120+
schedSpec = ptr.Ref(sched.String())
133121
}
134122

135123
templateVersion, err := client.TemplateVersion(cmd.Context(), template.ActiveVersionID)
@@ -230,7 +218,7 @@ func create() *cobra.Command {
230218
TemplateID: template.ID,
231219
Name: workspaceName,
232220
AutostartSchedule: schedSpec,
233-
TTLMillis: ptr.Ref(ttl.Milliseconds()),
221+
TTLMillis: ptr.Ref(stopAfter.Milliseconds()),
234222
ParameterValues: parameters,
235223
})
236224
if err != nil {
@@ -250,30 +238,7 @@ func create() *cobra.Command {
250238
cliui.AllowSkipPrompt(cmd)
251239
cliflag.StringVarP(cmd.Flags(), &templateName, "template", "t", "CODER_TEMPLATE_NAME", "", "Specify a template name.")
252240
cliflag.StringVarP(cmd.Flags(), &parameterFile, "parameter-file", "", "CODER_PARAMETER_FILE", "", "Specify a file path with parameter values.")
253-
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).")
254-
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).")
255-
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)")
256-
cliflag.StringVarP(cmd.Flags(), &tzName, "tz", "", "TZ", "UTC", "Specify your timezone location for workspace autostart (e.g. US/Central).")
257-
cliflag.DurationVarP(cmd.Flags(), &ttl, "ttl", "", "CODER_WORKSPACE_TTL", 8*time.Hour, "Specify a time-to-live (TTL) for the workspace (e.g. 8h).")
241+
cliflag.StringVarP(cmd.Flags(), &startAt, "start-at", "", "CODER_WORKSPACE_START_AT", "", "Specify the workspace autostart schedule. Check `coder schedule start --help` for the syntax.")
242+
cliflag.DurationVarP(cmd.Flags(), &stopAfter, "stop-after", "", "CODER_WORKSPACE_STOP_AFTER", 8*time.Hour, "Specify a duration after which the workspace should shut down (e.g. 8h).")
258243
return cmd
259244
}
260-
261-
func validSchedule(minute, hour, dow, tzName string, min time.Duration) (*string, error) {
262-
_, err := time.LoadLocation(tzName)
263-
if err != nil {
264-
return nil, xerrors.Errorf("Invalid workspace autostart timezone: %w", err)
265-
}
266-
267-
schedSpec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", tzName, minute, hour, dow)
268-
269-
sched, err := schedule.Weekly(schedSpec)
270-
if err != nil {
271-
return nil, err
272-
}
273-
274-
if schedMin := sched.Min(); schedMin < min {
275-
return nil, xerrors.Errorf("minimum autostart interval %s is above template constraint %s", schedMin, min)
276-
}
277-
278-
return &schedSpec, nil
279-
}

cli/create_test.go

Lines changed: 18 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/coder/coder/cli/clitest"
1515
"github.com/coder/coder/coderd/coderdtest"
1616
"github.com/coder/coder/coderd/database"
17-
"github.com/coder/coder/coderd/util/ptr"
1817
"github.com/coder/coder/codersdk"
1918
"github.com/coder/coder/provisioner/echo"
2019
"github.com/coder/coder/provisionersdk/proto"
@@ -38,11 +37,8 @@ func TestCreate(t *testing.T) {
3837
"create",
3938
"my-workspace",
4039
"--template", template.Name,
41-
"--tz", "US/Central",
42-
"--autostart-minute", "0",
43-
"--autostart-hour", "*/2",
44-
"--autostart-day-of-week", "MON-FRI",
45-
"--ttl", "8h",
40+
"--start-at", "9:30AM Mon-Fri US/Central",
41+
"--stop-after", "8h",
4642
}
4743
cmd, root := clitest.New(t, args...)
4844
clitest.SetupConfig(t, client, root)
@@ -70,103 +66,17 @@ func TestCreate(t *testing.T) {
7066
}
7167
}
7268
<-doneChan
73-
})
74-
75-
t.Run("AboveTemplateMaxTTL", func(t *testing.T) {
76-
t.Parallel()
77-
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
78-
user := coderdtest.CreateFirstUser(t, client)
79-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
80-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
81-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
82-
ctr.MaxTTLMillis = ptr.Ref((12 * time.Hour).Milliseconds())
83-
})
84-
args := []string{
85-
"create",
86-
"my-workspace",
87-
"--template", template.Name,
88-
"--ttl", "12h1m",
89-
"-y", // don't bother with waiting
90-
}
91-
cmd, root := clitest.New(t, args...)
92-
clitest.SetupConfig(t, client, root)
93-
pty := ptytest.New(t)
94-
cmd.SetIn(pty.Input())
95-
cmd.SetOut(pty.Output())
96-
err := cmd.Execute()
97-
assert.ErrorContains(t, err, "TTL must be below template maximum 12h0m0s")
98-
})
99-
100-
t.Run("BelowTemplateMinAutostartInterval", func(t *testing.T) {
101-
t.Parallel()
102-
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
103-
user := coderdtest.CreateFirstUser(t, client)
104-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
105-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
106-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
107-
ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds())
108-
})
109-
args := []string{
110-
"create",
111-
"my-workspace",
112-
"--template", template.Name,
113-
"--autostart-minute", "*", // Every minute
114-
"--autostart-hour", "*", // Every hour
115-
"-y", // don't bother with waiting
116-
}
117-
cmd, root := clitest.New(t, args...)
118-
clitest.SetupConfig(t, client, root)
119-
pty := ptytest.New(t)
120-
cmd.SetIn(pty.Input())
121-
cmd.SetOut(pty.Output())
122-
err := cmd.Execute()
123-
assert.ErrorContains(t, err, "minimum autostart interval 1m0s is above template constraint 1h0m0s")
124-
})
125-
126-
t.Run("CreateErrInvalidTz", func(t *testing.T) {
127-
t.Parallel()
128-
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
129-
user := coderdtest.CreateFirstUser(t, client)
130-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
131-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
132-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
133-
args := []string{
134-
"create",
135-
"my-workspace",
136-
"--template", template.Name,
137-
"--tz", "invalid",
138-
"-y",
139-
}
140-
cmd, root := clitest.New(t, args...)
141-
clitest.SetupConfig(t, client, root)
142-
pty := ptytest.New(t)
143-
cmd.SetIn(pty.Input())
144-
cmd.SetOut(pty.Output())
145-
err := cmd.Execute()
146-
assert.ErrorContains(t, err, "Invalid autostart schedule: Invalid workspace autostart timezone: unknown time zone invalid")
147-
})
14869

149-
t.Run("CreateErrInvalidTTL", func(t *testing.T) {
150-
t.Parallel()
151-
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
152-
user := coderdtest.CreateFirstUser(t, client)
153-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
154-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
155-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
156-
args := []string{
157-
"create",
158-
"my-workspace",
159-
"--template", template.Name,
160-
"--ttl", "0s",
161-
"-y",
70+
ws, err := client.WorkspaceByOwnerAndName(context.Background(), "testuser", "my-workspace", codersdk.WorkspaceOptions{})
71+
if assert.NoError(t, err, "expected workspace to be created") {
72+
assert.Equal(t, ws.TemplateName, template.Name)
73+
if assert.NotNil(t, ws.AutostartSchedule) {
74+
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
75+
}
76+
if assert.NotNil(t, ws.TTLMillis) {
77+
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
78+
}
16279
}
163-
cmd, root := clitest.New(t, args...)
164-
clitest.SetupConfig(t, client, root)
165-
pty := ptytest.New(t)
166-
cmd.SetIn(pty.Input())
167-
cmd.SetOut(pty.Output())
168-
err := cmd.Execute()
169-
assert.EqualError(t, err, "TTL must be at least 1 minute")
17080
})
17181

17282
t.Run("CreateFromListWithSkip", func(t *testing.T) {
@@ -197,7 +107,7 @@ func TestCreate(t *testing.T) {
197107
user := coderdtest.CreateFirstUser(t, client)
198108
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
199109
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
200-
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
110+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
201111
cmd, root := clitest.New(t, "create", "")
202112
clitest.SetupConfig(t, client, root)
203113
doneChan := make(chan struct{})
@@ -220,6 +130,12 @@ func TestCreate(t *testing.T) {
220130
pty.WriteLine(value)
221131
}
222132
<-doneChan
133+
134+
ws, err := client.WorkspaceByOwnerAndName(cmd.Context(), "testuser", "my-workspace", codersdk.WorkspaceOptions{})
135+
if assert.NoError(t, err, "expected workspace to be created") {
136+
assert.Equal(t, ws.TemplateName, template.Name)
137+
assert.Nil(t, ws.AutostartSchedule, "expected workspace autostart schedule to be nil")
138+
}
223139
})
224140

225141
t.Run("WithParameter", func(t *testing.T) {

0 commit comments

Comments
 (0)