mirror of
https://github.com/coder/coder.git
synced 2026-06-04 13:38:21 +00:00
6f82ad09c8
This makes the english consistent on flags, and improves the contrast for the placeholder color on dark themes.
297 lines
8.3 KiB
Go
297 lines
8.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/cli/cliui"
|
|
"github.com/coder/coder/coderd/autobuild/schedule"
|
|
"github.com/coder/coder/coderd/util/ptr"
|
|
"github.com/coder/coder/coderd/util/tz"
|
|
"github.com/coder/coder/codersdk"
|
|
)
|
|
|
|
const (
|
|
scheduleShowDescriptionLong = `Shows the following information for the given workspace:
|
|
* The automatic start schedule
|
|
* The next scheduled start time
|
|
* The duration after which it will stop
|
|
* The next scheduled stop time
|
|
`
|
|
scheduleStartDescriptionLong = `Schedules a workspace to regularly start at a specific time.
|
|
Schedule format: <start-time> [day-of-week] [location].
|
|
* Start-time (required) is accepted either in 12-hour (hh:mm{am|pm}) format, or 24-hour format hh:mm.
|
|
* Day-of-week (optional) allows specifying in the cron format, e.g. 1,3,5 or Mon-Fri.
|
|
Aliases such as @daily are not supported.
|
|
Default: * (every day)
|
|
* Location (optional) must be a valid location in the IANA timezone database.
|
|
If omitted, we will fall back to either the TZ environment variable or /etc/localtime.
|
|
You can check your corresponding location by visiting https://ipinfo.io - it shows in the demo widget on the right.
|
|
`
|
|
scheduleStopDescriptionLong = `Schedules a workspace to stop after a given duration has elapsed.
|
|
* Workspace runtime is measured from the time that the workspace build completed.
|
|
* The minimum scheduled stop time is 1 minute.
|
|
* The workspace template may place restrictions on the maximum shutdown time.
|
|
* Changes to workspace schedules only take effect upon the next build of the workspace,
|
|
and do not affect a running instance of a workspace.
|
|
|
|
When enabling scheduled stop, enter a duration in one of the following formats:
|
|
* 3h2m (3 hours and two minutes)
|
|
* 3h (3 hours)
|
|
* 2m (2 minutes)
|
|
* 2 (2 minutes)
|
|
`
|
|
scheduleOverrideDescriptionLong = `Override the stop time of a currently running workspace instance.
|
|
* The new stop time is calculated from *now*.
|
|
* The new stop time must be at least 30 minutes in the future.
|
|
* The workspace template may restrict the maximum workspace runtime.
|
|
`
|
|
)
|
|
|
|
func schedules() *cobra.Command {
|
|
scheduleCmd := &cobra.Command{
|
|
Annotations: workspaceCommand,
|
|
Use: "schedule { show | start | stop | override } <workspace>",
|
|
Short: "Schedule automated start and stop times for workspaces",
|
|
}
|
|
|
|
scheduleCmd.AddCommand(
|
|
scheduleShow(),
|
|
scheduleStart(),
|
|
scheduleStop(),
|
|
scheduleOverride(),
|
|
)
|
|
|
|
return scheduleCmd
|
|
}
|
|
|
|
func scheduleShow() *cobra.Command {
|
|
showCmd := &cobra.Command{
|
|
Use: "show <workspace-name>",
|
|
Short: "Show workspace schedule",
|
|
Long: scheduleShowDescriptionLong,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := CreateClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
workspace, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return displaySchedule(workspace, cmd.OutOrStdout())
|
|
},
|
|
}
|
|
return showCmd
|
|
}
|
|
|
|
func scheduleStart() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "start <workspace-name> { <start-time> [day-of-week] [location] | manual }",
|
|
Example: formatExamples(
|
|
example{
|
|
Description: "Set the workspace to start at 9:30am (in Dublin) from Monday to Friday",
|
|
Command: "coder schedule start my-workspace 9:30AM Mon-Fri Europe/Dublin",
|
|
},
|
|
),
|
|
Short: "Edit workspace start schedule",
|
|
Long: scheduleStartDescriptionLong,
|
|
Args: cobra.RangeArgs(2, 4),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := CreateClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
workspace, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var schedStr *string
|
|
if args[1] != "manual" {
|
|
sched, err := parseCLISchedule(args[1:]...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
schedStr = ptr.Ref(sched.String())
|
|
}
|
|
|
|
err = client.UpdateWorkspaceAutostart(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
|
Schedule: schedStr,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return displaySchedule(updated, cmd.OutOrStdout())
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
func scheduleStop() *cobra.Command {
|
|
return &cobra.Command{
|
|
Args: cobra.ExactArgs(2),
|
|
Use: "stop <workspace-name> { <duration> | manual }",
|
|
Example: formatExamples(
|
|
example{
|
|
Command: "coder schedule stop my-workspace 2h30m",
|
|
},
|
|
),
|
|
Short: "Edit workspace stop schedule",
|
|
Long: scheduleStopDescriptionLong,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := CreateClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
workspace, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var durMillis *int64
|
|
if args[1] != "manual" {
|
|
dur, err := parseDuration(args[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
durMillis = ptr.Ref(dur.Milliseconds())
|
|
}
|
|
|
|
if err := client.UpdateWorkspaceTTL(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
|
TTLMillis: durMillis,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return displaySchedule(updated, cmd.OutOrStdout())
|
|
},
|
|
}
|
|
}
|
|
|
|
func scheduleOverride() *cobra.Command {
|
|
overrideCmd := &cobra.Command{
|
|
Args: cobra.ExactArgs(2),
|
|
Use: "override-stop <workspace-name> <duration from now>",
|
|
Example: formatExamples(
|
|
example{
|
|
Command: "coder schedule override-stop my-workspace 90m",
|
|
},
|
|
),
|
|
Short: "Edit stop time of active workspace",
|
|
Long: scheduleOverrideDescriptionLong,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
overrideDuration, err := parseDuration(args[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client, err := CreateClient(cmd)
|
|
if err != nil {
|
|
return xerrors.Errorf("create client: %w", err)
|
|
}
|
|
|
|
workspace, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return xerrors.Errorf("get workspace: %w", err)
|
|
}
|
|
|
|
loc, err := tz.TimezoneIANA()
|
|
if err != nil {
|
|
loc = time.UTC // best effort
|
|
}
|
|
|
|
if overrideDuration < 29*time.Minute {
|
|
_, _ = fmt.Fprintf(
|
|
cmd.OutOrStdout(),
|
|
"Please specify a duration of at least 30 minutes.\n",
|
|
)
|
|
return nil
|
|
}
|
|
|
|
newDeadline := time.Now().In(loc).Add(overrideDuration)
|
|
if err := client.PutExtendWorkspace(cmd.Context(), workspace.ID, codersdk.PutExtendWorkspaceRequest{
|
|
Deadline: newDeadline,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := namedWorkspace(cmd, client, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return displaySchedule(updated, cmd.OutOrStdout())
|
|
},
|
|
}
|
|
return overrideCmd
|
|
}
|
|
|
|
func displaySchedule(workspace codersdk.Workspace, out io.Writer) error {
|
|
loc, err := tz.TimezoneIANA()
|
|
if err != nil {
|
|
loc = time.UTC // best effort
|
|
}
|
|
|
|
var (
|
|
schedStart = "manual"
|
|
schedStop = "manual"
|
|
schedNextStart = "-"
|
|
schedNextStop = "-"
|
|
)
|
|
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
|
|
sched, err := schedule.Weekly(ptr.NilToEmpty(workspace.AutostartSchedule))
|
|
if err != nil {
|
|
// This should never happen.
|
|
_, _ = fmt.Fprintf(out, "Invalid autostart schedule %q for workspace %s: %s\n", *workspace.AutostartSchedule, workspace.Name, err.Error())
|
|
return nil
|
|
}
|
|
schedNext := sched.Next(time.Now()).In(sched.Location())
|
|
schedStart = fmt.Sprintf("%s %s (%s)", sched.Time(), sched.DaysOfWeek(), sched.Location())
|
|
schedNextStart = schedNext.Format(timeFormat + " on " + dateFormat)
|
|
}
|
|
|
|
if !ptr.NilOrZero(workspace.TTLMillis) {
|
|
d := time.Duration(*workspace.TTLMillis) * time.Millisecond
|
|
schedStop = durationDisplay(d) + " after start"
|
|
}
|
|
|
|
if !workspace.LatestBuild.Deadline.IsZero() {
|
|
if workspace.LatestBuild.Transition != "start" {
|
|
schedNextStop = "-"
|
|
} else {
|
|
schedNextStop = workspace.LatestBuild.Deadline.Time.In(loc).Format(timeFormat + " on " + dateFormat)
|
|
schedNextStop = fmt.Sprintf("%s (in %s)", schedNextStop, durationDisplay(time.Until(workspace.LatestBuild.Deadline.Time)))
|
|
}
|
|
}
|
|
|
|
tw := cliui.Table()
|
|
tw.AppendRow(table.Row{"Starts at", schedStart})
|
|
tw.AppendRow(table.Row{"Starts next", schedNextStart})
|
|
tw.AppendRow(table.Row{"Stops at", schedStop})
|
|
tw.AppendRow(table.Row{"Stops next", schedNextStop})
|
|
|
|
_, _ = fmt.Fprintln(out, tw.Render())
|
|
return nil
|
|
}
|