Thanks to visit codestin.com
Credit goes to github.com

Skip to content

autostart/autostop: move to traditional 5-valued cron string for compatibility #1049

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 6 commits into from
Apr 18, 2022
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
39 changes: 26 additions & 13 deletions cli/workspaceautostart.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"os"
"time"

"github.com/spf13/cobra"
Expand All @@ -11,20 +12,16 @@ import (
)

const autostartDescriptionLong = `To have your workspace build automatically at a regular time you can enable autostart.
When enabling autostart, provide a schedule. This schedule is in cron format except only
the following fields are allowed:
- minute
- hour
- day of week

For example, to start your workspace every weekday at 9.30 am, provide the schedule '30 9 1-5'.`
When enabling autostart, provide the minute, hour, and day(s) of week.
The default schedule is at 09:00 in your local timezone (TZ env, UTC by default).
`

func workspaceAutostart() *cobra.Command {
autostartCmd := &cobra.Command{
Use: "autostart enable <workspace> <schedule>",
Use: "autostart enable <workspace>",
Short: "schedule a workspace to automatically start at a regular time",
Long: autostartDescriptionLong,
Example: "coder workspaces autostart enable my-workspace '30 9 1-5'",
Example: "coder workspaces autostart enable my-workspace --minute 30 --hour 9 --days 1-5 --tz Europe/Dublin",
Hidden: true, // TODO(cian): un-hide when autostart scheduling implemented
}

Expand All @@ -35,22 +32,28 @@ func workspaceAutostart() *cobra.Command {
}

func workspaceAutostartEnable() *cobra.Command {
return &cobra.Command{
// yes some of these are technically numbers but the cron library will do that work
var autostartMinute string
var autostartHour string
var autostartDayOfWeek string
var autostartTimezone string
cmd := &cobra.Command{
Use: "enable <workspace_name> <schedule>",
ValidArgsFunction: validArgsWorkspaceName,
Args: cobra.ExactArgs(2),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}

workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0])
spec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", autostartTimezone, autostartMinute, autostartHour, autostartDayOfWeek)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: nice!

validSchedule, err := schedule.Weekly(spec)
if err != nil {
return err
}

validSchedule, err := schedule.Weekly(args[1])
workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0])
if err != nil {
return err
}
Expand All @@ -67,6 +70,16 @@ func workspaceAutostartEnable() *cobra.Command {
return nil
},
}

cmd.Flags().StringVar(&autostartMinute, "minute", "0", "autostart minute")
cmd.Flags().StringVar(&autostartHour, "hour", "9", "autostart hour")
cmd.Flags().StringVar(&autostartDayOfWeek, "days", "1-5", "autostart day(s) of week")
tzEnv := os.Getenv("TZ")
if tzEnv == "" {
tzEnv = "UTC"
}
Comment on lines +77 to +80
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: nice 😎

cmd.Flags().StringVar(&autostartTimezone, "tz", tzEnv, "autostart timezone")
return cmd
}

func workspaceAutostartDisable() *cobra.Command {
Expand Down
50 changes: 16 additions & 34 deletions cli/workspaceautostart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cli_test
import (
"bytes"
"context"
"fmt"
"os"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -27,11 +29,13 @@ func TestWorkspaceAutostart(t *testing.T) {
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
sched = "CRON_TZ=Europe/Dublin 30 9 1-5"
tz = "Europe/Dublin"
cmdArgs = []string{"workspaces", "autostart", "enable", workspace.Name, "--minute", "30", "--hour", "9", "--days", "1-5", "--tz", tz}
sched = "CRON_TZ=Europe/Dublin 30 9 * * 1-5"
stdoutBuf = &bytes.Buffer{}
)

cmd, root := clitest.New(t, "workspaces", "autostart", "enable", workspace.Name, sched)
cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)

Expand Down Expand Up @@ -68,10 +72,9 @@ func TestWorkspaceAutostart(t *testing.T) {
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
sched = "CRON_TZ=Europe/Dublin 30 9 1-5"
)

cmd, root := clitest.New(t, "workspaces", "autostart", "enable", "doesnotexist", sched)
cmd, root := clitest.New(t, "workspaces", "autostart", "enable", "doesnotexist")
clitest.SetupConfig(t, client, root)

err := cmd.Execute()
Expand All @@ -96,34 +99,7 @@ func TestWorkspaceAutostart(t *testing.T) {
require.ErrorContains(t, err, "status code 404: no workspace found by name", "unexpected error")
})

t.Run("Enable_InvalidSchedule", func(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
sched = "sdfasdfasdf asdf asdf"
)

cmd, root := clitest.New(t, "workspaces", "autostart", "enable", workspace.Name, sched)
clitest.SetupConfig(t, client, root)

err := cmd.Execute()
require.ErrorContains(t, err, "failed to parse int from sdfasdfasdf: strconv.Atoi:", "unexpected error")

// Ensure nothing happened
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Empty(t, updated.AutostartSchedule, "expected autostart schedule to be empty")
})

t.Run("Enable_NoSchedule", func(t *testing.T) {
t.Run("Enable_DefaultSchedule", func(t *testing.T) {
t.Parallel()

var (
Expand All @@ -137,15 +113,21 @@ func TestWorkspaceAutostart(t *testing.T) {
workspace = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
)

// check current TZ env var
currTz := os.Getenv("TZ")
if currTz == "" {
currTz = "UTC"
}
expectedSchedule := fmt.Sprintf("CRON_TZ=%s 0 9 * * 1-5", currTz)
cmd, root := clitest.New(t, "workspaces", "autostart", "enable", workspace.Name)
clitest.SetupConfig(t, client, root)

err := cmd.Execute()
require.ErrorContains(t, err, "accepts 2 arg(s), received 1", "unexpected error")
require.NoError(t, err, "unexpected error")

// Ensure nothing happened
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Empty(t, updated.AutostartSchedule, "expected autostart schedule to be empty")
require.Equal(t, expectedSchedule, updated.AutostartSchedule, "expected default autostart schedule")
})
}
41 changes: 27 additions & 14 deletions cli/workspaceautostop.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"os"
"time"

"github.com/spf13/cobra"
Expand All @@ -11,20 +12,16 @@ import (
)

const autostopDescriptionLong = `To have your workspace stop automatically at a regular time you can enable autostop.
When enabling autostop, provide a schedule. This schedule is in cron format except only
the following fields are allowed:
- minute
- hour
- day of week

For example, to stop your workspace every weekday at 5.30 pm, provide the schedule '30 17 1-5'.`
When enabling autostop, provide the minute, hour, and day(s) of week.
The default autostop schedule is at 18:00 in your local timezone (TZ env, UTC by default).
`

func workspaceAutostop() *cobra.Command {
autostopCmd := &cobra.Command{
Use: "autostop enable <workspace> <schedule>",
Short: "schedule a workspace to automatically start at a regular time",
Use: "autostop enable <workspace>",
Short: "schedule a workspace to automatically stop at a regular time",
Long: autostopDescriptionLong,
Example: "coder workspaces autostop enable my-workspace '30 17 1-5'",
Example: "coder workspaces autostop enable my-workspace --minute 0 --hour 18 --days 1-5 -tz Europe/Dublin",
Hidden: true, // TODO(cian): un-hide when autostop scheduling implemented
}

Expand All @@ -35,22 +32,28 @@ func workspaceAutostop() *cobra.Command {
}

func workspaceAutostopEnable() *cobra.Command {
return &cobra.Command{
// yes some of these are technically numbers but the cron library will do that work
var autostopMinute string
var autostopHour string
var autostopDayOfWeek string
var autostopTimezone string
cmd := &cobra.Command{
Use: "enable <workspace_name> <schedule>",
ValidArgsFunction: validArgsWorkspaceName,
Args: cobra.ExactArgs(2),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}

workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0])
spec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", autostopTimezone, autostopMinute, autostopHour, autostopDayOfWeek)
validSchedule, err := schedule.Weekly(spec)
if err != nil {
return err
}

validSchedule, err := schedule.Weekly(args[1])
workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0])
if err != nil {
return err
}
Expand All @@ -67,6 +70,16 @@ func workspaceAutostopEnable() *cobra.Command {
return nil
},
}

cmd.Flags().StringVar(&autostopMinute, "minute", "0", "autostop minute")
cmd.Flags().StringVar(&autostopHour, "hour", "18", "autostop hour")
cmd.Flags().StringVar(&autostopDayOfWeek, "days", "1-5", "autostop day(s) of week")
tzEnv := os.Getenv("TZ")
if tzEnv == "" {
tzEnv = "UTC"
}
cmd.Flags().StringVar(&autostopTimezone, "tz", tzEnv, "autostop timezone")
return cmd
}

func workspaceAutostopDisable() *cobra.Command {
Expand Down
48 changes: 15 additions & 33 deletions cli/workspaceautostop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cli_test
import (
"bytes"
"context"
"fmt"
"os"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -27,11 +29,12 @@ func TestWorkspaceAutostop(t *testing.T) {
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
sched = "CRON_TZ=Europe/Dublin 30 9 1-5"
cmdArgs = []string{"workspaces", "autostop", "enable", workspace.Name, "--minute", "30", "--hour", "17", "--days", "1-5", "--tz", "Europe/Dublin"}
sched = "CRON_TZ=Europe/Dublin 30 17 * * 1-5"
stdoutBuf = &bytes.Buffer{}
)

cmd, root := clitest.New(t, "workspaces", "autostop", "enable", workspace.Name, sched)
cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)

Expand Down Expand Up @@ -68,10 +71,9 @@ func TestWorkspaceAutostop(t *testing.T) {
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
sched = "CRON_TZ=Europe/Dublin 30 9 1-5"
)

cmd, root := clitest.New(t, "workspaces", "autostop", "enable", "doesnotexist", sched)
cmd, root := clitest.New(t, "workspaces", "autostop", "enable", "doesnotexist")
clitest.SetupConfig(t, client, root)

err := cmd.Execute()
Expand All @@ -96,7 +98,7 @@ func TestWorkspaceAutostop(t *testing.T) {
require.ErrorContains(t, err, "status code 404: no workspace found by name", "unexpected error")
})

t.Run("Enable_InvalidSchedule", func(t *testing.T) {
t.Run("Enable_DefaultSchedule", func(t *testing.T) {
t.Parallel()

var (
Expand All @@ -108,44 +110,24 @@ func TestWorkspaceAutostop(t *testing.T) {
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
sched = "sdfasdfasdf asdf asdf"
)

cmd, root := clitest.New(t, "workspaces", "autostop", "enable", workspace.Name, sched)
clitest.SetupConfig(t, client, root)

err := cmd.Execute()
require.ErrorContains(t, err, "failed to parse int from sdfasdfasdf: strconv.Atoi:", "unexpected error")

// Ensure nothing happened
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Empty(t, updated.AutostopSchedule, "expected autostop schedule to be empty")
})

t.Run("Enable_NoSchedule", func(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID)
)
// check current TZ env var
currTz := os.Getenv("TZ")
if currTz == "" {
currTz = "UTC"
}
expectedSchedule := fmt.Sprintf("CRON_TZ=%s 0 18 * * 1-5", currTz)

cmd, root := clitest.New(t, "workspaces", "autostop", "enable", workspace.Name)
clitest.SetupConfig(t, client, root)

err := cmd.Execute()
require.ErrorContains(t, err, "accepts 2 arg(s), received 1", "unexpected error")
require.NoError(t, err, "unexpected error")

// Ensure nothing happened
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Empty(t, updated.AutostopSchedule, "expected autostop schedule to be empty")
require.Equal(t, expectedSchedule, updated.AutostopSchedule, "expected default autostop schedule")
})
}
Loading