Implement --job for rerunning a specific actions job
This commit is contained in:
parent
c38ca830be
commit
bf5801e646
2 changed files with 129 additions and 28 deletions
|
|
@ -20,6 +20,7 @@ type RerunOptions struct {
|
|||
|
||||
RunID string
|
||||
OnlyFailed bool
|
||||
JobID string
|
||||
|
||||
Prompt bool
|
||||
}
|
||||
|
|
@ -38,12 +39,22 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm
|
|||
// support `-R, --repo` override
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
|
||||
if len(args) > 0 {
|
||||
if len(args) == 0 && opts.JobID == "" {
|
||||
if !opts.IO.CanPrompt() {
|
||||
return cmdutil.FlagErrorf("run or job ID required when not running interactively")
|
||||
} else {
|
||||
opts.Prompt = true
|
||||
}
|
||||
} else if len(args) > 0 {
|
||||
opts.RunID = args[0]
|
||||
} else if !opts.IO.CanPrompt() {
|
||||
return cmdutil.FlagErrorf("run ID required when not running interactively")
|
||||
} else {
|
||||
opts.Prompt = true
|
||||
}
|
||||
|
||||
if opts.RunID != "" && opts.JobID != "" {
|
||||
opts.RunID = ""
|
||||
if opts.IO.CanPrompt() {
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintf(opts.IO.ErrOut, "%s both run and job IDs specified; ignoring run ID\n", cs.WarningIcon())
|
||||
}
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
|
|
@ -54,6 +65,7 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm
|
|||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.OnlyFailed, "failed", false, "Rerun only failed jobs")
|
||||
cmd.Flags().StringVarP(&opts.JobID, "job", "j", "", "Rerun a specific job from a run, including dependencies")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -70,10 +82,23 @@ func runRerun(opts *RerunOptions) error {
|
|||
return fmt.Errorf("failed to determine base repo: %w", err)
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
runID := opts.RunID
|
||||
jobID := opts.JobID
|
||||
var selectedJob *shared.Job
|
||||
|
||||
if jobID != "" {
|
||||
opts.IO.StartProgressIndicator()
|
||||
selectedJob, err = shared.GetJob(client, repo, jobID)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get job: %w", err)
|
||||
}
|
||||
runID = fmt.Sprintf("%d", selectedJob.RunID)
|
||||
}
|
||||
|
||||
if opts.Prompt {
|
||||
cs := opts.IO.ColorScheme()
|
||||
runs, err := shared.GetRunsWithFilter(client, repo, nil, 10, func(run shared.Run) bool {
|
||||
if run.Status != shared.Completed {
|
||||
return false
|
||||
|
|
@ -94,40 +119,73 @@ func runRerun(opts *RerunOptions) error {
|
|||
}
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
run, err := shared.GetRun(client, repo, runID)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get run: %w", err)
|
||||
if opts.JobID != "" {
|
||||
err = rerunJob(client, repo, selectedJob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.IO.CanPrompt() {
|
||||
fmt.Fprintf(opts.IO.Out, "%s Requested rerun of job %s on run %s\n",
|
||||
cs.SuccessIcon(),
|
||||
cs.Cyanf("%d", selectedJob.ID),
|
||||
cs.Cyanf("%d", selectedJob.RunID))
|
||||
}
|
||||
} else {
|
||||
opts.IO.StartProgressIndicator()
|
||||
run, err := shared.GetRun(client, repo, runID)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get run: %w", err)
|
||||
}
|
||||
|
||||
err = rerunRun(client, repo, run, opts.OnlyFailed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.IO.CanPrompt() {
|
||||
onlyFailedMsg := ""
|
||||
if opts.OnlyFailed {
|
||||
onlyFailedMsg = "(failed jobs) "
|
||||
}
|
||||
fmt.Fprintf(opts.IO.Out, "%s Requested rerun %sof run %s\n",
|
||||
cs.SuccessIcon(),
|
||||
onlyFailedMsg,
|
||||
cs.Cyanf("%d", run.ID))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rerunRun(client *api.Client, repo ghrepo.Interface, run *shared.Run, onlyFailed bool) error {
|
||||
runVerb := "rerun"
|
||||
if opts.OnlyFailed {
|
||||
if onlyFailed {
|
||||
runVerb = "rerun-failed-jobs"
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("repos/%s/actions/runs/%d/%s", ghrepo.FullName(repo), run.ID, runVerb)
|
||||
|
||||
err = client.REST(repo.RepoHost(), "POST", path, nil, nil)
|
||||
err := client.REST(repo.RepoHost(), "POST", path, nil, nil)
|
||||
if err != nil {
|
||||
var httpError api.HTTPError
|
||||
if errors.As(err, &httpError) && httpError.StatusCode == 403 {
|
||||
return fmt.Errorf("run %d cannot be rerun; its workflow file may be broken.", run.ID)
|
||||
return fmt.Errorf("run %d cannot be rerun; its workflow file may be broken", run.ID)
|
||||
}
|
||||
return fmt.Errorf("failed to rerun: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rerunJob(client *api.Client, repo ghrepo.Interface, job *shared.Job) error {
|
||||
path := fmt.Sprintf("repos/%s/actions/jobs/%d/rerun", ghrepo.FullName(repo), job.ID)
|
||||
|
||||
err := client.REST(repo.RepoHost(), "POST", path, nil, nil)
|
||||
if err != nil {
|
||||
var httpError api.HTTPError
|
||||
if errors.As(err, &httpError) && httpError.StatusCode == 403 {
|
||||
return fmt.Errorf("job %d cannot be rerun", job.ID)
|
||||
}
|
||||
return fmt.Errorf("failed to rerun: %w", err)
|
||||
}
|
||||
|
||||
if opts.IO.CanPrompt() {
|
||||
cs := opts.IO.ColorScheme()
|
||||
onlyFailedMsg := ""
|
||||
if opts.OnlyFailed {
|
||||
onlyFailedMsg = "(failed jobs) "
|
||||
}
|
||||
fmt.Fprintf(opts.IO.Out, "%s Requested rerun %sof run %s\n",
|
||||
cs.SuccessIcon(),
|
||||
onlyFailedMsg,
|
||||
cs.Cyanf("%d", run.ID))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,33 @@ func TestNewCmdRerun(t *testing.T) {
|
|||
OnlyFailed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with arg job",
|
||||
tty: true,
|
||||
cli: "--job 1234",
|
||||
wants: RerunOptions{
|
||||
JobID: "1234",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with args job and runID ignores runID",
|
||||
tty: true,
|
||||
cli: "1234 --job 5678",
|
||||
wants: RerunOptions{
|
||||
JobID: "5678",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with arg job with no ID fails",
|
||||
tty: true,
|
||||
cli: "--job",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "with arg job with no ID no tty fails",
|
||||
cli: "--job",
|
||||
wantsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -151,6 +178,22 @@ func TestRerun(t *testing.T) {
|
|||
},
|
||||
wantOut: "✓ Requested rerun (failed jobs) of run 1234\n",
|
||||
},
|
||||
{
|
||||
name: "arg including a specific job",
|
||||
tty: true,
|
||||
opts: &RerunOptions{
|
||||
JobID: "20", // 20 is shared.FailedJob.ID
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/20"),
|
||||
httpmock.JSONResponse(shared.FailedJob))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/jobs/20/rerun"),
|
||||
httpmock.StringResponse("{}"))
|
||||
},
|
||||
wantOut: "✓ Requested rerun of job 20 on run 1234\n",
|
||||
},
|
||||
{
|
||||
name: "prompt",
|
||||
tty: true,
|
||||
|
|
@ -209,7 +252,7 @@ func TestRerun(t *testing.T) {
|
|||
httpmock.StatusStringResponse(403, "no"))
|
||||
},
|
||||
wantErr: true,
|
||||
errOut: "run 3 cannot be rerun; its workflow file may be broken.",
|
||||
errOut: "run 3 cannot be rerun; its workflow file may be broken",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue