Skip to content

Commit 6edee0b

Browse files
authored
Merge pull request #435 from depot/luke/jj-xoqspmnyvtoz
feat: `depot ci run list`
2 parents bcd48c6 + 2a8f807 commit 6edee0b

File tree

5 files changed

+671
-55
lines changed

5 files changed

+671
-55
lines changed

pkg/api/ci.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,49 @@ func CIRun(ctx context.Context, token string, req *civ1.RunRequest) (*civ1.RunRe
5858
return resp.Msg, nil
5959
}
6060

61+
// CIListRuns returns CI runs, paginating as needed to collect up to `limit` results.
62+
// If limit is 0, all results are returned.
63+
func CIListRuns(ctx context.Context, token string, statuses []civ1.CIRunStatus, limit int32) ([]*civ1.ListRunsResponseRun, error) {
64+
client := newCIServiceClient()
65+
var allRuns []*civ1.ListRunsResponseRun
66+
var pageToken string
67+
68+
for {
69+
pageSize := limit
70+
if limit > 0 {
71+
remaining := limit - int32(len(allRuns))
72+
if remaining <= 0 {
73+
break
74+
}
75+
pageSize = remaining
76+
}
77+
78+
req := &civ1.ListRunsRequest{
79+
Status: statuses,
80+
PageSize: pageSize,
81+
PageToken: pageToken,
82+
}
83+
resp, err := client.ListRuns(ctx, WithAuthentication(connect.NewRequest(req), token))
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
allRuns = append(allRuns, resp.Msg.Runs...)
89+
90+
if limit > 0 && int32(len(allRuns)) >= limit {
91+
allRuns = allRuns[:limit]
92+
break
93+
}
94+
95+
if resp.Msg.NextPageToken == "" {
96+
break
97+
}
98+
pageToken = resp.Msg.NextPageToken
99+
}
100+
101+
return allRuns, nil
102+
}
103+
61104
func newCISecretServiceClient() civ1connect.SecretServiceClient {
62105
baseURL := baseURLFunc()
63106
return civ1connect.NewSecretServiceClient(getHTTPClient(baseURL), baseURL, WithUserAgent())

pkg/cmd/ci/run.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ci
22

33
import (
44
"crypto/sha256"
5+
"encoding/json"
56
"fmt"
67
"os"
78
"os/exec"
@@ -201,6 +202,8 @@ This command is in beta and subject to change.`,
201202
cmd.Flags().StringSliceVar(&jobNames, "job", nil, "Job name(s) to run (repeatable; omit to run all)")
202203
cmd.Flags().IntVar(&sshAfterStep, "ssh-after-step", 0, "1-based step index to insert a tmate debug step after (requires single --job)")
203204

205+
cmd.AddCommand(NewCmdRunList())
206+
204207
return cmd
205208
}
206209

@@ -399,3 +402,155 @@ func injectTmateStep(jobs map[string]interface{}, jobName string, afterStep int,
399402

400403
return nil
401404
}
405+
406+
// validStatuses are the user-facing status names accepted by --status.
407+
var validStatuses = []string{"queued", "running", "finished", "failed", "cancelled"}
408+
409+
func parseStatus(s string) (civ1.CIRunStatus, error) {
410+
switch strings.ToLower(s) {
411+
case "queued":
412+
return civ1.CIRunStatus_CI_RUN_STATUS_QUEUED, nil
413+
case "running":
414+
return civ1.CIRunStatus_CI_RUN_STATUS_RUNNING, nil
415+
case "finished":
416+
return civ1.CIRunStatus_CI_RUN_STATUS_FINISHED, nil
417+
case "failed":
418+
return civ1.CIRunStatus_CI_RUN_STATUS_FAILED, nil
419+
case "cancelled":
420+
return civ1.CIRunStatus_CI_RUN_STATUS_CANCELLED, nil
421+
default:
422+
return 0, fmt.Errorf("invalid status %q, valid values: %s", s, strings.Join(validStatuses, ", "))
423+
}
424+
}
425+
426+
func formatStatus(s civ1.CIRunStatus) string {
427+
switch s {
428+
case civ1.CIRunStatus_CI_RUN_STATUS_QUEUED:
429+
return "queued"
430+
case civ1.CIRunStatus_CI_RUN_STATUS_RUNNING:
431+
return "running"
432+
case civ1.CIRunStatus_CI_RUN_STATUS_FINISHED:
433+
return "finished"
434+
case civ1.CIRunStatus_CI_RUN_STATUS_FAILED:
435+
return "failed"
436+
case civ1.CIRunStatus_CI_RUN_STATUS_CANCELLED:
437+
return "cancelled"
438+
default:
439+
return "unknown"
440+
}
441+
}
442+
443+
func NewCmdRunList() *cobra.Command {
444+
var (
445+
token string
446+
statuses []string
447+
n int32
448+
output string
449+
)
450+
451+
cmd := &cobra.Command{
452+
Use: "list",
453+
Short: "List CI runs",
454+
Long: `List CI runs for your organization.`,
455+
Example: ` # List runs (defaults to queued and running)
456+
depot ci run list
457+
458+
# List failed runs
459+
depot ci run list --status failed
460+
461+
# List finished and failed runs
462+
depot ci run list --status finished --status failed
463+
464+
# List the 5 most recent runs
465+
depot ci run list -n 5
466+
467+
# Output as JSON
468+
depot ci run list --output json`,
469+
Aliases: []string{"ls"},
470+
RunE: func(cmd *cobra.Command, args []string) error {
471+
if n <= 0 {
472+
return fmt.Errorf("page size (-n) must be greater than 0")
473+
}
474+
475+
ctx := cmd.Context()
476+
477+
tokenVal, err := helpers.ResolveOrgAuth(ctx, token)
478+
if err != nil {
479+
return err
480+
}
481+
if tokenVal == "" {
482+
return fmt.Errorf("missing API token, please run `depot login`")
483+
}
484+
485+
var protoStatuses []civ1.CIRunStatus
486+
for _, s := range statuses {
487+
ps, err := parseStatus(s)
488+
if err != nil {
489+
return err
490+
}
491+
protoStatuses = append(protoStatuses, ps)
492+
}
493+
494+
runs, err := api.CIListRuns(ctx, tokenVal, protoStatuses, n)
495+
if err != nil {
496+
return fmt.Errorf("failed to list runs: %w", err)
497+
}
498+
499+
if output == "json" {
500+
enc := json.NewEncoder(os.Stdout)
501+
enc.SetIndent("", " ")
502+
return enc.Encode(runs)
503+
}
504+
505+
if len(runs) == 0 {
506+
fmt.Println("No runs found.")
507+
return nil
508+
}
509+
510+
fmt.Printf("%-24s %-30s %-12s %-10s %-12s %s\n", "RUN ID", "REPO", "SHA", "STATUS", "TRIGGER", "CREATED")
511+
fmt.Printf("%-24s %-30s %-12s %-10s %-12s %s\n",
512+
strings.Repeat("-", 24),
513+
strings.Repeat("-", 30),
514+
strings.Repeat("-", 12),
515+
strings.Repeat("-", 10),
516+
strings.Repeat("-", 12),
517+
strings.Repeat("-", 20),
518+
)
519+
520+
for _, run := range runs {
521+
repo := run.Repo
522+
if len(repo) > 30 {
523+
repo = repo[:27] + "..."
524+
}
525+
526+
sha := run.Sha
527+
if len(sha) > 12 {
528+
sha = sha[:12]
529+
}
530+
531+
trigger := run.Trigger
532+
if len(trigger) > 12 {
533+
trigger = trigger[:9] + "..."
534+
}
535+
536+
fmt.Printf("%-24s %-30s %-12s %-10s %-12s %s\n",
537+
run.RunId,
538+
repo,
539+
sha,
540+
formatStatus(run.Status),
541+
trigger,
542+
run.CreatedAt,
543+
)
544+
}
545+
546+
return nil
547+
},
548+
}
549+
550+
cmd.Flags().StringVar(&token, "token", "", "Depot API token")
551+
cmd.Flags().StringSliceVar(&statuses, "status", nil, "Filter by status (repeatable: queued, running, finished, failed, cancelled)")
552+
cmd.Flags().Int32VarP(&n, "n", "n", 50, "Number of runs to return")
553+
cmd.Flags().StringVarP(&output, "output", "o", "", "Output format (json)")
554+
555+
return cmd
556+
}

0 commit comments

Comments
 (0)