diff --git a/pkg/cmd/run/rerun/rerun.go b/pkg/cmd/run/rerun/rerun.go index 45f231f90..4df1b6ada 100644 --- a/pkg/cmd/run/rerun/rerun.go +++ b/pkg/cmd/run/rerun/rerun.go @@ -1,8 +1,11 @@ package rerun import ( + "bytes" + "encoding/json" "errors" "fmt" + "io" "net/http" "github.com/cli/cli/v2/api" @@ -21,6 +24,7 @@ type RerunOptions struct { RunID string OnlyFailed bool JobID string + Debug bool Prompt bool } @@ -62,6 +66,7 @@ func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Comm cmd.Flags().BoolVar(&opts.OnlyFailed, "failed", false, "Rerun only failed jobs, including dependencies") cmd.Flags().StringVarP(&opts.JobID, "job", "j", "", "Rerun a specific job from a run, including dependencies") + cmd.Flags().BoolVarP(&opts.Debug, "debug", "d", false, "Rerun with debug logging") return cmd } @@ -115,16 +120,22 @@ func runRerun(opts *RerunOptions) error { } } + debugMsg := "" + if opts.Debug { + debugMsg = " with debug logging enabled" + } + if opts.JobID != "" { - err = rerunJob(client, repo, selectedJob) + err = rerunJob(client, repo, selectedJob, opts.Debug) if err != nil { return err } if opts.IO.IsStdoutTTY() { - fmt.Fprintf(opts.IO.Out, "%s Requested rerun of job %s on run %s\n", + fmt.Fprintf(opts.IO.Out, "%s Requested rerun of job %s on run %s%s\n", cs.SuccessIcon(), cs.Cyanf("%d", selectedJob.ID), - cs.Cyanf("%d", selectedJob.RunID)) + cs.Cyanf("%d", selectedJob.RunID), + debugMsg) } } else { opts.IO.StartProgressIndicator() @@ -134,7 +145,7 @@ func runRerun(opts *RerunOptions) error { return fmt.Errorf("failed to get run: %w", err) } - err = rerunRun(client, repo, run, opts.OnlyFailed) + err = rerunRun(client, repo, run, opts.OnlyFailed, opts.Debug) if err != nil { return err } @@ -143,25 +154,31 @@ func runRerun(opts *RerunOptions) error { if opts.OnlyFailed { onlyFailedMsg = "(failed jobs) " } - fmt.Fprintf(opts.IO.Out, "%s Requested rerun %sof run %s\n", + fmt.Fprintf(opts.IO.Out, "%s Requested rerun %sof run %s%s\n", cs.SuccessIcon(), onlyFailedMsg, - cs.Cyanf("%d", run.ID)) + cs.Cyanf("%d", run.ID), + debugMsg) } } return nil } -func rerunRun(client *api.Client, repo ghrepo.Interface, run *shared.Run, onlyFailed bool) error { +func rerunRun(client *api.Client, repo ghrepo.Interface, run *shared.Run, onlyFailed, debug bool) error { runVerb := "rerun" if onlyFailed { runVerb = "rerun-failed-jobs" } + body, err := requestBody(debug) + if err != nil { + return fmt.Errorf("failed to create rerun body: %w", err) + } + 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, body, nil) if err != nil { var httpError api.HTTPError if errors.As(err, &httpError) && httpError.StatusCode == 403 { @@ -172,10 +189,15 @@ func rerunRun(client *api.Client, repo ghrepo.Interface, run *shared.Run, onlyFa return nil } -func rerunJob(client *api.Client, repo ghrepo.Interface, job *shared.Job) error { +func rerunJob(client *api.Client, repo ghrepo.Interface, job *shared.Job, debug bool) error { + body, err := requestBody(debug) + if err != nil { + return fmt.Errorf("failed to create rerun body: %w", err) + } + path := fmt.Sprintf("repos/%s/actions/jobs/%d/rerun", ghrepo.FullName(repo), job.ID) - err := client.REST(repo.RepoHost(), "POST", path, nil, nil) + err = client.REST(repo.RepoHost(), "POST", path, body, nil) if err != nil { var httpError api.HTTPError if errors.As(err, &httpError) && httpError.StatusCode == 403 { @@ -185,3 +207,23 @@ func rerunJob(client *api.Client, repo ghrepo.Interface, job *shared.Job) error } return nil } + +type RerunPayload struct { + Debug bool `json:"enable_debug_logging"` +} + +func requestBody(debug bool) (io.Reader, error) { + if !debug { + return nil, nil + } + params := &RerunPayload{ + debug, + } + + body := &bytes.Buffer{} + enc := json.NewEncoder(body) + if err := enc.Encode(params); err != nil { + return nil, err + } + return body, nil +} diff --git a/pkg/cmd/run/rerun/rerun_test.go b/pkg/cmd/run/rerun/rerun_test.go index fddc76ed1..5d9dd3dc0 100644 --- a/pkg/cmd/run/rerun/rerun_test.go +++ b/pkg/cmd/run/rerun/rerun_test.go @@ -2,6 +2,7 @@ package rerun import ( "bytes" + "encoding/json" "io" "net/http" "testing" @@ -92,6 +93,31 @@ func TestNewCmdRerun(t *testing.T) { cli: "--job", wantsErr: true, }, + { + name: "debug nontty", + cli: "4321 --debug", + wants: RerunOptions{ + RunID: "4321", + Debug: true, + }, + }, + { + name: "debug tty", + tty: true, + cli: "--debug", + wants: RerunOptions{ + Prompt: true, + Debug: true, + }, + }, + { + name: "debug off", + cli: "4321 --debug=false", + wants: RerunOptions{ + RunID: "4321", + Debug: false, + }, + }, } for _, tt := range tests { @@ -142,6 +168,7 @@ func TestRerun(t *testing.T) { wantErr bool errOut string wantOut string + wantDebug bool }{ { name: "arg", @@ -192,6 +219,61 @@ func TestRerun(t *testing.T) { }, wantOut: "✓ Requested rerun of job 20 on run 1234\n", }, + { + name: "arg including debug", + tty: true, + opts: &RerunOptions{ + RunID: "1234", + Debug: true, + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), + httpmock.JSONResponse(shared.FailedRun)) + reg.Register( + httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun"), + httpmock.StringResponse("{}")) + }, + wantOut: "✓ Requested rerun of run 1234 with debug logging enabled\n", + wantDebug: true, + }, + { + name: "arg including onlyFailed and debug", + tty: true, + opts: &RerunOptions{ + RunID: "1234", + OnlyFailed: true, + Debug: true, + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), + httpmock.JSONResponse(shared.FailedRun)) + reg.Register( + httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun-failed-jobs"), + httpmock.StringResponse("{}")) + }, + wantOut: "✓ Requested rerun (failed jobs) of run 1234 with debug logging enabled\n", + wantDebug: true, + }, + { + name: "arg including a specific job and debug", + tty: true, + opts: &RerunOptions{ + JobID: "20", // 20 is shared.FailedJob.ID + Debug: true, + }, + 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 with debug logging enabled\n", + wantDebug: true, + }, { name: "prompt", tty: true, @@ -286,6 +368,24 @@ func TestRerun(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tt.wantOut, stdout.String()) reg.Verify(t) + + for _, d := range reg.Requests { + if d.Method != "POST" { + continue + } + + if !tt.wantDebug { + assert.Nil(t, d.Body) + continue + } + + data, err := io.ReadAll(d.Body) + assert.NoError(t, err) + var payload RerunPayload + err = json.Unmarshal(data, &payload) + assert.NoError(t, err) + assert.Equal(t, tt.wantDebug, payload.Debug) + } }) } }