mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
feat(run): show runs like gh, watch them live, and download artifacts
run list prints a table with a status icon, the workflow name, title, branch, event, elapsed time and age, and the total from the server; runs carry workflow, workflow_path and run_number in JSON. run view shows the run's state and trigger, its jobs with their steps and durations, the next steps and the run's URL; --json includes the jobs, --log prints their logs and --web opens the run. Run commands accept #N and run URLs, and ask for a recent run in a terminal when the argument is left out. run watch redraws the run on the alternate screen, then prints it with a summary; an expired --timeout names the run and suspects a missing runner when no job started. run cancel no longer asks for confirmation, rerun refuses unfinished runs up front, reruns a single job with --job and reports the new attempt, and delete names the run in its prompt. run logs follows several jobs with --follow, prefixing lines with the job's name, selects jobs by name or with --failed, and downloads the archive with --output or --dir. run download extracts the artifacts of a run by name, and artifact list shows sizes and expiry. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
19 files changed
+2636
-646
No files matched your search
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -129,161 +128,3 @@ func (a *application) workflowCommand() *cobra.Command {
|
||||
func workflowRow(item services.Workflow) []terminal.Cell {
|
||||
return terminal.Row(item.ID, item.Name, item.State)
|
||||
}
|
||||
|
||||
func runRow(item services.Run) []terminal.Cell {
|
||||
return []terminal.Cell{{Text: strconv.FormatInt(item.ID, 10), Link: item.URL}, {Text: item.Title}, {Text: item.Status}, {Text: item.Conclusion}, {Text: item.Branch}, {Text: item.Event}}
|
||||
}
|
||||
|
||||
func (a *application) runCommand() *cobra.Command {
|
||||
group := &cobra.Command{Use: "run", Short: "Inspect and watch Actions runs"}
|
||||
group.AddCommand(a.runLogsCommand(), a.artifactCommand(), a.runJobsCommand())
|
||||
var filters services.RunListOptions
|
||||
var started timeRange
|
||||
list := &cobra.Command{Use: "list", Short: "List workflow runs", Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
var err error
|
||||
if filters.Since, filters.Until, err = started.parse(a.deps.Now()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := filters.Validate(); err != nil {
|
||||
return clierr.Wrap(clierr.Usage, err)
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
items, err := (services.Actions{Client: t.client, Repository: t.Repo}).Runs(cmd.Context(), filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeList(a, cmd, items, runRow)
|
||||
}}
|
||||
pageFlags(list, &filters.Limit, &filters.Page)
|
||||
started.register(list, "since", "Runs created at or after a date, RFC3339 timestamp, or duration ago (24h)", "until", "Runs created at or before a date, RFC3339 timestamp, or duration ago")
|
||||
list.Flags().StringVar(&filters.Branch, "branch", "", "Filter by branch name")
|
||||
list.Flags().StringVar(&filters.Event, "event", "", "Filter by triggering event")
|
||||
list.Flags().StringVar(&filters.Actor, "actor", "", "Filter by triggering username")
|
||||
list.Flags().StringVar(&filters.Commit, "commit", "", "Filter by triggering commit SHA")
|
||||
list.Flags().StringVar(&filters.Workflow, "workflow", "", "Filter by workflow file name")
|
||||
list.Flags().StringSliceVar(&filters.Statuses, "status", nil, "Filter status: pending, waiting, requested, action_required, queued, in_progress, completed, success, failure, skipped, neutral, cancelled, timed_out") //nolint:misspell // Gitea API spelling.
|
||||
group.AddCommand(list)
|
||||
var includeJobs bool
|
||||
viewRun := &cobra.Command{Use: "view <id>", Short: "View a workflow run", Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := services.Actions{Client: t.client, Repository: t.Repo}
|
||||
item, err := service.Run(cmd.Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !includeJobs || a.options.json != "" {
|
||||
return writeItem(a, cmd, item, runRow)
|
||||
}
|
||||
jobs, err := service.Jobs(cmd.Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobLine := func(job services.Job) []terminal.Cell { return append(terminal.Row("job"), jobRow(job)...) }
|
||||
color := a.colorEnabled()
|
||||
return a.writeHuman(cmd, table([]services.Run{item}, runRow, color)+table(jobs, jobLine, color))
|
||||
}}
|
||||
viewRun.Flags().BoolVar(&includeJobs, "jobs", true, "Include job summaries after the run details")
|
||||
group.AddCommand(viewRun)
|
||||
var interval, timeout time.Duration
|
||||
watch := &cobra.Command{Use: "watch <id>", Short: "Wait for completion; fail if the run fails", Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if interval <= 0 || timeout <= 0 {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("--interval and --timeout must be positive"))
|
||||
}
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(cmd.Context(), timeout)
|
||||
defer cancel()
|
||||
result, err := (services.Actions{Client: t.client, Repository: t.Repo}).Watch(ctx, id, interval, func(item services.Run) error {
|
||||
if a.options.json != "" {
|
||||
_, err := fmt.Fprintf(cmd.ErrOrStderr(), "run %d: %s %s\n", item.ID, item.Status, item.Conclusion)
|
||||
return err
|
||||
}
|
||||
return writeItem(a, cmd, item, runRow)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.options.json != "" {
|
||||
if err := writeItem(a, cmd, result, runRow); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !services.SuccessfulRun(result) {
|
||||
return errors.New("workflow run did not succeed")
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
watch.Flags().DurationVar(&interval, "interval", 5*time.Second, "Polling interval")
|
||||
watch.Flags().DurationVar(&timeout, "timeout", time.Hour, "Maximum time to wait")
|
||||
group.AddCommand(watch)
|
||||
var forceCancel bool
|
||||
cancelRun := &cobra.Command{Use: "cancel <id>", Short: "Cancel a workflow run while preserving its history", Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
message := fmt.Sprintf("Cancel run %d", id)
|
||||
if forceCancel {
|
||||
message = fmt.Sprintf("Force-cancel run %d without waiting for runners", id)
|
||||
}
|
||||
if err := a.confirm(cmd, message); err != nil {
|
||||
return err
|
||||
}
|
||||
item, err := (services.Actions{Client: target.client, Repository: target.Repo}).CancelRun(cmd.Context(), id, forceCancel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeItem(a, cmd, item, runRow)
|
||||
}}
|
||||
cancelRun.Flags().BoolVar(&forceCancel, "force", false, "Cancel without waiting for runner acknowledgment")
|
||||
group.AddCommand(cancelRun)
|
||||
var failedOnly bool
|
||||
rerun := &cobra.Command{Use: "rerun <id>", Short: "Rerun a workflow run", Annotations: textOutput("rerun does not return a resource"), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return (services.Actions{Client: t.client, Repository: t.Repo}).Rerun(cmd.Context(), id, failedOnly)
|
||||
}}
|
||||
rerun.Flags().BoolVar(&failedOnly, "failed", false, "Rerun failed jobs only")
|
||||
group.AddCommand(rerun)
|
||||
group.AddCommand(&cobra.Command{Use: "delete <id>", Short: "Delete a workflow run", Annotations: textOutput("run deletion does not return a resource"), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.confirm(cmd, fmt.Sprintf("Delete run %d", id)); err != nil {
|
||||
return err
|
||||
}
|
||||
return (services.Actions{Client: t.client, Repository: t.Repo}).DeleteRun(cmd.Context(), id)
|
||||
}})
|
||||
return group
|
||||
}
|
||||
Reference in new issue
Block a user