From a072ff89e6284d8b7cd5edbb2535e0095722c671 Mon Sep 17 00:00:00 2001 From: johnstcn <public@cianjohnston.ie> Date: Fri, 17 Jun 2022 17:33:43 +0000 Subject: [PATCH] fix: cli: create: use new autostart format, opt-in by default --- cli/create.go | 65 ++++++------------------ cli/create_test.go | 120 +++++++-------------------------------------- 2 files changed, 33 insertions(+), 152 deletions(-) diff --git a/cli/create.go b/cli/create.go index 1a0c89cc62a27..258e272c01dbf 100644 --- a/cli/create.go +++ b/cli/create.go @@ -10,21 +10,17 @@ import ( "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" - "github.com/coder/coder/coderd/autobuild/schedule" "github.com/coder/coder/coderd/util/ptr" "github.com/coder/coder/codersdk" ) func create() *cobra.Command { var ( - autostartMinute string - autostartHour string - autostartDow string - parameterFile string - templateName string - ttl time.Duration - tzName string - workspaceName string + parameterFile string + templateName string + startAt string + stopAfter time.Duration + workspaceName string ) cmd := &cobra.Command{ Annotations: workspaceCommand, @@ -115,21 +111,13 @@ func create() *cobra.Command { } } - schedSpec, err := validSchedule( - autostartMinute, - autostartHour, - autostartDow, - tzName, - time.Duration(template.MinAutostartIntervalMillis)*time.Millisecond, - ) - if err != nil { - return xerrors.Errorf("Invalid autostart schedule: %w", err) - } - if ttl < time.Minute { - return xerrors.Errorf("TTL must be at least 1 minute") - } - if ttlMax := time.Duration(template.MaxTTLMillis) * time.Millisecond; ttl > ttlMax { - return xerrors.Errorf("TTL must be below template maximum %s", ttlMax) + var schedSpec *string + if startAt != "" { + sched, err := parseCLISchedule(startAt) + if err != nil { + return err + } + schedSpec = ptr.Ref(sched.String()) } templateVersion, err := client.TemplateVersion(cmd.Context(), template.ActiveVersionID) @@ -230,7 +218,7 @@ func create() *cobra.Command { TemplateID: template.ID, Name: workspaceName, AutostartSchedule: schedSpec, - TTLMillis: ptr.Ref(ttl.Milliseconds()), + TTLMillis: ptr.Ref(stopAfter.Milliseconds()), ParameterValues: parameters, }) if err != nil { @@ -250,30 +238,7 @@ func create() *cobra.Command { cliui.AllowSkipPrompt(cmd) cliflag.StringVarP(cmd.Flags(), &templateName, "template", "t", "CODER_TEMPLATE_NAME", "", "Specify a template name.") cliflag.StringVarP(cmd.Flags(), ¶meterFile, "parameter-file", "", "CODER_PARAMETER_FILE", "", "Specify a file path with parameter values.") - 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).") - 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).") - 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)") - cliflag.StringVarP(cmd.Flags(), &tzName, "tz", "", "TZ", "UTC", "Specify your timezone location for workspace autostart (e.g. US/Central).") - cliflag.DurationVarP(cmd.Flags(), &ttl, "ttl", "", "CODER_WORKSPACE_TTL", 8*time.Hour, "Specify a time-to-live (TTL) for the workspace (e.g. 8h).") + cliflag.StringVarP(cmd.Flags(), &startAt, "start-at", "", "CODER_WORKSPACE_START_AT", "", "Specify the workspace autostart schedule. Check `coder schedule start --help` for the syntax.") + 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).") return cmd } - -func validSchedule(minute, hour, dow, tzName string, min time.Duration) (*string, error) { - _, err := time.LoadLocation(tzName) - if err != nil { - return nil, xerrors.Errorf("Invalid workspace autostart timezone: %w", err) - } - - schedSpec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", tzName, minute, hour, dow) - - sched, err := schedule.Weekly(schedSpec) - if err != nil { - return nil, err - } - - if schedMin := sched.Min(); schedMin < min { - return nil, xerrors.Errorf("minimum autostart interval %s is above template constraint %s", schedMin, min) - } - - return &schedSpec, nil -} diff --git a/cli/create_test.go b/cli/create_test.go index 63654df7378b6..cd069706ea71e 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -14,7 +14,6 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/database" - "github.com/coder/coder/coderd/util/ptr" "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" @@ -38,11 +37,8 @@ func TestCreate(t *testing.T) { "create", "my-workspace", "--template", template.Name, - "--tz", "US/Central", - "--autostart-minute", "0", - "--autostart-hour", "*/2", - "--autostart-day-of-week", "MON-FRI", - "--ttl", "8h", + "--start-at", "9:30AM Mon-Fri US/Central", + "--stop-after", "8h", } cmd, root := clitest.New(t, args...) clitest.SetupConfig(t, client, root) @@ -70,103 +66,17 @@ func TestCreate(t *testing.T) { } } <-doneChan - }) - - t.Run("AboveTemplateMaxTTL", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MaxTTLMillis = ptr.Ref((12 * time.Hour).Milliseconds()) - }) - args := []string{ - "create", - "my-workspace", - "--template", template.Name, - "--ttl", "12h1m", - "-y", // don't bother with waiting - } - cmd, root := clitest.New(t, args...) - clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - cmd.SetIn(pty.Input()) - cmd.SetOut(pty.Output()) - err := cmd.Execute() - assert.ErrorContains(t, err, "TTL must be below template maximum 12h0m0s") - }) - - t.Run("BelowTemplateMinAutostartInterval", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds()) - }) - args := []string{ - "create", - "my-workspace", - "--template", template.Name, - "--autostart-minute", "*", // Every minute - "--autostart-hour", "*", // Every hour - "-y", // don't bother with waiting - } - cmd, root := clitest.New(t, args...) - clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - cmd.SetIn(pty.Input()) - cmd.SetOut(pty.Output()) - err := cmd.Execute() - assert.ErrorContains(t, err, "minimum autostart interval 1m0s is above template constraint 1h0m0s") - }) - - t.Run("CreateErrInvalidTz", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - args := []string{ - "create", - "my-workspace", - "--template", template.Name, - "--tz", "invalid", - "-y", - } - cmd, root := clitest.New(t, args...) - clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - cmd.SetIn(pty.Input()) - cmd.SetOut(pty.Output()) - err := cmd.Execute() - assert.ErrorContains(t, err, "Invalid autostart schedule: Invalid workspace autostart timezone: unknown time zone invalid") - }) - t.Run("CreateErrInvalidTTL", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - args := []string{ - "create", - "my-workspace", - "--template", template.Name, - "--ttl", "0s", - "-y", + ws, err := client.WorkspaceByOwnerAndName(context.Background(), "testuser", "my-workspace", codersdk.WorkspaceOptions{}) + if assert.NoError(t, err, "expected workspace to be created") { + assert.Equal(t, ws.TemplateName, template.Name) + if assert.NotNil(t, ws.AutostartSchedule) { + assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri") + } + if assert.NotNil(t, ws.TTLMillis) { + assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds()) + } } - cmd, root := clitest.New(t, args...) - clitest.SetupConfig(t, client, root) - pty := ptytest.New(t) - cmd.SetIn(pty.Input()) - cmd.SetOut(pty.Output()) - err := cmd.Execute() - assert.EqualError(t, err, "TTL must be at least 1 minute") }) t.Run("CreateFromListWithSkip", func(t *testing.T) { @@ -197,7 +107,7 @@ func TestCreate(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - _ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) cmd, root := clitest.New(t, "create", "") clitest.SetupConfig(t, client, root) doneChan := make(chan struct{}) @@ -220,6 +130,12 @@ func TestCreate(t *testing.T) { pty.WriteLine(value) } <-doneChan + + ws, err := client.WorkspaceByOwnerAndName(cmd.Context(), "testuser", "my-workspace", codersdk.WorkspaceOptions{}) + if assert.NoError(t, err, "expected workspace to be created") { + assert.Equal(t, ws.TemplateName, template.Name) + assert.Nil(t, ws.AutostartSchedule, "expected workspace autostart schedule to be nil") + } }) t.Run("WithParameter", func(t *testing.T) {