@@ -3,9 +3,9 @@ package cli
3
3
import (
4
4
"fmt"
5
5
"io"
6
+ "strings"
6
7
"time"
7
8
8
- "github.com/jedib0t/go-pretty/v6/table"
9
9
"golang.org/x/xerrors"
10
10
11
11
"github.com/coder/coder/v2/cli/clibase"
@@ -17,7 +17,7 @@ import (
17
17
)
18
18
19
19
const (
20
- scheduleShowDescriptionLong = `Shows the following information for the given workspace:
20
+ scheduleShowDescriptionLong = `Shows the following information for the given workspace(s) :
21
21
* The automatic start schedule
22
22
* The next scheduled start time
23
23
* The duration after which it will stop
@@ -72,25 +72,67 @@ func (r *RootCmd) schedules() *clibase.Cmd {
72
72
return scheduleCmd
73
73
}
74
74
75
+ // scheduleShow() is just a wrapper for list() with some different defaults.
75
76
func (r * RootCmd ) scheduleShow () * clibase.Cmd {
77
+ var (
78
+ filter cliui.WorkspaceFilter
79
+ formatter = cliui .NewOutputFormatter (
80
+ cliui .TableFormat (
81
+ []scheduleListRow {},
82
+ []string {
83
+ "workspace" ,
84
+ "starts at" ,
85
+ "starts next" ,
86
+ "stops after" ,
87
+ "stops next" ,
88
+ },
89
+ ),
90
+ cliui .JSONFormat (),
91
+ )
92
+ )
76
93
client := new (codersdk.Client )
77
94
showCmd := & clibase.Cmd {
78
- Use : "show <workspace-name >" ,
79
- Short : "Show workspace schedule " ,
95
+ Use : "show <workspace | --search <query> | --all >" ,
96
+ Short : "Show workspace schedules " ,
80
97
Long : scheduleShowDescriptionLong ,
81
98
Middleware : clibase .Chain (
82
- clibase .RequireNArgs ( 1 ),
99
+ clibase .RequireRangeArgs ( 0 , 1 ),
83
100
r .InitClient (client ),
84
101
),
85
102
Handler : func (inv * clibase.Invocation ) error {
86
- workspace , err := namedWorkspace (inv .Context (), client , inv .Args [0 ])
103
+ // To preserve existing behavior, if an argument is passed we will
104
+ // only show the schedule for that workspace.
105
+ // This will clobber the search query if one is passed.
106
+ f := filter .Filter ()
107
+ if len (inv .Args ) == 1 {
108
+ // If the argument contains a slash, we assume it's a full owner/name reference
109
+ if strings .Contains (inv .Args [0 ], "/" ) {
110
+ _ , workspaceName , err := splitNamedWorkspace (inv .Args [0 ])
111
+ if err != nil {
112
+ return err
113
+ }
114
+ f .FilterQuery = fmt .Sprintf ("name:%s" , workspaceName )
115
+ } else {
116
+ // Otherwise, we assume it's a workspace name owned by the current user
117
+ f .FilterQuery = fmt .Sprintf ("owner:me name:%s" , inv .Args [0 ])
118
+ }
119
+ }
120
+ res , err := queryConvertWorkspaces (inv .Context (), client , f , scheduleListRowFromWorkspace )
87
121
if err != nil {
88
122
return err
89
123
}
90
124
91
- return displaySchedule (workspace , inv .Stdout )
125
+ out , err := formatter .Format (inv .Context (), res )
126
+ if err != nil {
127
+ return err
128
+ }
129
+
130
+ _ , err = fmt .Fprintln (inv .Stdout , out )
131
+ return err
92
132
},
93
133
}
134
+ filter .AttachOptions (& showCmd .Options )
135
+ formatter .AttachOptions (& showCmd .Options )
94
136
return showCmd
95
137
}
96
138
@@ -242,50 +284,52 @@ func (r *RootCmd) scheduleOverride() *clibase.Cmd {
242
284
return overrideCmd
243
285
}
244
286
245
- func displaySchedule (workspace codersdk.Workspace , out io.Writer ) error {
246
- loc , err := tz .TimezoneIANA ()
287
+ func displaySchedule (ws codersdk.Workspace , out io.Writer ) error {
288
+ rows := []workspaceListRow {workspaceListRowFromWorkspace (time .Now (), ws )}
289
+ rendered , err := cliui .DisplayTable (rows , "workspace" , []string {
290
+ "workspace" , "starts at" , "starts next" , "stops after" , "stops next" ,
291
+ })
247
292
if err != nil {
248
- loc = time . UTC // best effort
293
+ return err
249
294
}
295
+ _ , err = fmt .Fprintln (out , rendered )
296
+ return err
297
+ }
250
298
251
- var (
252
- schedStart = "manual"
253
- schedStop = "manual"
254
- schedNextStart = "-"
255
- schedNextStop = "-"
256
- )
299
+ // scheduleListRow is a row in the schedule list.
300
+ // this is required for proper JSON output.
301
+ type scheduleListRow struct {
302
+ WorkspaceName string `json:"workspace" table:"workspace,default_sort"`
303
+ StartsAt string `json:"starts_at" table:"starts at"`
304
+ StartsNext string `json:"starts_next" table:"starts next"`
305
+ StopsAfter string `json:"stops_after" table:"stops after"`
306
+ StopsNext string `json:"stops_next" table:"stops next"`
307
+ }
308
+
309
+ func scheduleListRowFromWorkspace (now time.Time , workspace codersdk.Workspace ) scheduleListRow {
310
+ autostartDisplay := ""
311
+ nextStartDisplay := ""
257
312
if ! ptr .NilOrEmpty (workspace .AutostartSchedule ) {
258
- sched , err := cron .Weekly (ptr .NilToEmpty (workspace .AutostartSchedule ))
259
- if err != nil {
260
- // This should never happen.
261
- _ , _ = fmt .Fprintf (out , "Invalid autostart schedule %q for workspace %s: %s\n " , * workspace .AutostartSchedule , workspace .Name , err .Error ())
262
- return nil
313
+ if sched , err := cron .Weekly (* workspace .AutostartSchedule ); err == nil {
314
+ autostartDisplay = sched .Humanize ()
315
+ nextStartDisplay = timeDisplay (sched .Next (now ))
263
316
}
264
- schedNext := sched .Next (time .Now ()).In (sched .Location ())
265
- schedStart = fmt .Sprintf ("%s %s (%s)" , sched .Time (), sched .DaysOfWeek (), sched .Location ())
266
- schedNextStart = schedNext .Format (timeFormat + " on " + dateFormat )
267
317
}
268
318
319
+ autostopDisplay := ""
320
+ nextStopDisplay := ""
269
321
if ! ptr .NilOrZero (workspace .TTLMillis ) {
270
- d := time .Duration (* workspace .TTLMillis ) * time .Millisecond
271
- schedStop = durationDisplay (d ) + " after start"
272
- }
273
-
274
- if ! workspace .LatestBuild .Deadline .IsZero () {
275
- if workspace .LatestBuild .Transition != "start" {
276
- schedNextStop = "-"
277
- } else {
278
- schedNextStop = workspace .LatestBuild .Deadline .Time .In (loc ).Format (timeFormat + " on " + dateFormat )
279
- schedNextStop = fmt .Sprintf ("%s (in %s)" , schedNextStop , durationDisplay (time .Until (workspace .LatestBuild .Deadline .Time )))
322
+ dur := time .Duration (* workspace .TTLMillis ) * time .Millisecond
323
+ autostopDisplay = durationDisplay (dur )
324
+ if ! workspace .LatestBuild .Deadline .IsZero () && workspace .LatestBuild .Transition == codersdk .WorkspaceTransitionStart {
325
+ nextStopDisplay = timeDisplay (workspace .LatestBuild .Deadline .Time )
280
326
}
281
327
}
282
-
283
- tw := cliui .Table ()
284
- tw .AppendRow (table.Row {"Starts at" , schedStart })
285
- tw .AppendRow (table.Row {"Starts next" , schedNextStart })
286
- tw .AppendRow (table.Row {"Stops at" , schedStop })
287
- tw .AppendRow (table.Row {"Stops next" , schedNextStop })
288
-
289
- _ , _ = fmt .Fprintln (out , tw .Render ())
290
- return nil
328
+ return scheduleListRow {
329
+ WorkspaceName : workspace .OwnerName + "/" + workspace .Name ,
330
+ StartsAt : autostartDisplay ,
331
+ StartsNext : nextStartDisplay ,
332
+ StopsAfter : autostopDisplay ,
333
+ StopsNext : nextStopDisplay ,
334
+ }
291
335
}
0 commit comments