github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/run/rerun/rerun.go (about) 1 package rerun 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 8 "github.com/cli/cli/api" 9 "github.com/cli/cli/internal/ghrepo" 10 "github.com/cli/cli/pkg/cmd/run/shared" 11 "github.com/cli/cli/pkg/cmdutil" 12 "github.com/cli/cli/pkg/iostreams" 13 "github.com/spf13/cobra" 14 ) 15 16 type RerunOptions struct { 17 HttpClient func() (*http.Client, error) 18 IO *iostreams.IOStreams 19 BaseRepo func() (ghrepo.Interface, error) 20 21 RunID string 22 23 Prompt bool 24 } 25 26 func NewCmdRerun(f *cmdutil.Factory, runF func(*RerunOptions) error) *cobra.Command { 27 opts := &RerunOptions{ 28 IO: f.IOStreams, 29 HttpClient: f.HttpClient, 30 } 31 32 cmd := &cobra.Command{ 33 Use: "rerun [<run-id>]", 34 Short: "Rerun a failed run", 35 Args: cobra.MaximumNArgs(1), 36 RunE: func(cmd *cobra.Command, args []string) error { 37 // support `-R, --repo` override 38 opts.BaseRepo = f.BaseRepo 39 40 if len(args) > 0 { 41 opts.RunID = args[0] 42 } else if !opts.IO.CanPrompt() { 43 return &cmdutil.FlagError{Err: errors.New("run ID required when not running interactively")} 44 } else { 45 opts.Prompt = true 46 } 47 48 if runF != nil { 49 return runF(opts) 50 } 51 return runRerun(opts) 52 }, 53 } 54 55 return cmd 56 } 57 58 func runRerun(opts *RerunOptions) error { 59 c, err := opts.HttpClient() 60 if err != nil { 61 return fmt.Errorf("failed to create http client: %w", err) 62 } 63 client := api.NewClientFromHTTP(c) 64 65 repo, err := opts.BaseRepo() 66 if err != nil { 67 return fmt.Errorf("failed to determine base repo: %w", err) 68 } 69 70 runID := opts.RunID 71 72 if opts.Prompt { 73 cs := opts.IO.ColorScheme() 74 runs, err := shared.GetRunsWithFilter(client, repo, 10, func(run shared.Run) bool { 75 if run.Status != shared.Completed { 76 return false 77 } 78 // TODO StartupFailure indiciates a bad yaml file; such runs can never be 79 // rerun. But hiding them from the prompt might confuse people? 80 return run.Conclusion != shared.Success && run.Conclusion != shared.StartupFailure 81 }) 82 if err != nil { 83 return fmt.Errorf("failed to get runs: %w", err) 84 } 85 if len(runs) == 0 { 86 return errors.New("no recent runs have failed; please specify a specific run ID") 87 } 88 runID, err = shared.PromptForRun(cs, runs) 89 if err != nil { 90 return err 91 } 92 } 93 94 opts.IO.StartProgressIndicator() 95 run, err := shared.GetRun(client, repo, runID) 96 opts.IO.StopProgressIndicator() 97 if err != nil { 98 return fmt.Errorf("failed to get run: %w", err) 99 } 100 101 path := fmt.Sprintf("repos/%s/actions/runs/%d/rerun", ghrepo.FullName(repo), run.ID) 102 103 err = client.REST(repo.RepoHost(), "POST", path, nil, nil) 104 if err != nil { 105 var httpError api.HTTPError 106 if errors.As(err, &httpError) && httpError.StatusCode == 403 { 107 return fmt.Errorf("run %d cannot be rerun; its workflow file may be broken.", run.ID) 108 } 109 return fmt.Errorf("failed to rerun: %w", err) 110 } 111 112 if opts.IO.CanPrompt() { 113 cs := opts.IO.ColorScheme() 114 fmt.Fprintf(opts.IO.Out, "%s Requested rerun of run %s\n", 115 cs.SuccessIcon(), 116 cs.Cyanf("%d", run.ID)) 117 } 118 119 return nil 120 }