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  }