github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/workflow/shared/shared.go (about)

     1  package shared
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  	"net/url"
     8  	"path"
     9  	"strings"
    10  
    11  	"github.com/AlecAivazis/survey/v2"
    12  	"github.com/cli/cli/api"
    13  	"github.com/cli/cli/internal/ghrepo"
    14  	"github.com/cli/cli/pkg/iostreams"
    15  	"github.com/cli/cli/pkg/prompt"
    16  )
    17  
    18  const (
    19  	Active           WorkflowState = "active"
    20  	DisabledManually WorkflowState = "disabled_manually"
    21  )
    22  
    23  type WorkflowState string
    24  
    25  type Workflow struct {
    26  	Name  string
    27  	ID    int64
    28  	Path  string
    29  	State WorkflowState
    30  }
    31  
    32  type WorkflowsPayload struct {
    33  	Workflows []Workflow
    34  }
    35  
    36  func (w *Workflow) Disabled() bool {
    37  	return w.State != Active
    38  }
    39  
    40  func (w *Workflow) Base() string {
    41  	return path.Base(w.Path)
    42  }
    43  
    44  func GetWorkflows(client *api.Client, repo ghrepo.Interface, limit int) ([]Workflow, error) {
    45  	perPage := limit
    46  	page := 1
    47  	if limit > 100 || limit == 0 {
    48  		perPage = 100
    49  	}
    50  
    51  	workflows := []Workflow{}
    52  
    53  	for {
    54  		if limit > 0 && len(workflows) == limit {
    55  			break
    56  		}
    57  		var result WorkflowsPayload
    58  
    59  		path := fmt.Sprintf("repos/%s/actions/workflows?per_page=%d&page=%d", ghrepo.FullName(repo), perPage, page)
    60  
    61  		err := client.REST(repo.RepoHost(), "GET", path, nil, &result)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  
    66  		for _, workflow := range result.Workflows {
    67  			workflows = append(workflows, workflow)
    68  			if limit > 0 && len(workflows) == limit {
    69  				break
    70  			}
    71  		}
    72  
    73  		if len(result.Workflows) < perPage {
    74  			break
    75  		}
    76  
    77  		page++
    78  	}
    79  
    80  	return workflows, nil
    81  }
    82  
    83  type FilteredAllError struct {
    84  	error
    85  }
    86  
    87  func SelectWorkflow(workflows []Workflow, promptMsg string, states []WorkflowState) (*Workflow, error) {
    88  	filtered := []Workflow{}
    89  	candidates := []string{}
    90  	for _, workflow := range workflows {
    91  		for _, state := range states {
    92  			if workflow.State == state {
    93  				filtered = append(filtered, workflow)
    94  				candidates = append(candidates, fmt.Sprintf("%s (%s)", workflow.Name, workflow.Base()))
    95  				break
    96  			}
    97  		}
    98  	}
    99  
   100  	if len(candidates) == 0 {
   101  		return nil, FilteredAllError{errors.New("")}
   102  	}
   103  
   104  	var selected int
   105  
   106  	err := prompt.SurveyAskOne(&survey.Select{
   107  		Message:  promptMsg,
   108  		Options:  candidates,
   109  		PageSize: 15,
   110  	}, &selected)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	return &filtered[selected], nil
   116  }
   117  
   118  func FindWorkflow(client *api.Client, repo ghrepo.Interface, workflowSelector string, states []WorkflowState) ([]Workflow, error) {
   119  	if workflowSelector == "" {
   120  		return nil, errors.New("empty workflow selector")
   121  	}
   122  
   123  	workflow, err := getWorkflowByID(client, repo, workflowSelector)
   124  	if err == nil {
   125  		return []Workflow{*workflow}, nil
   126  	} else {
   127  		var httpErr api.HTTPError
   128  		if !errors.As(err, &httpErr) || httpErr.StatusCode != 404 {
   129  			return nil, err
   130  		}
   131  	}
   132  
   133  	return getWorkflowsByName(client, repo, workflowSelector, states)
   134  }
   135  
   136  func getWorkflowByID(client *api.Client, repo ghrepo.Interface, ID string) (*Workflow, error) {
   137  	var workflow Workflow
   138  
   139  	err := client.REST(repo.RepoHost(), "GET",
   140  		fmt.Sprintf("repos/%s/actions/workflows/%s", ghrepo.FullName(repo), ID),
   141  		nil, &workflow)
   142  
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	return &workflow, nil
   148  }
   149  
   150  func getWorkflowsByName(client *api.Client, repo ghrepo.Interface, name string, states []WorkflowState) ([]Workflow, error) {
   151  	workflows, err := GetWorkflows(client, repo, 0)
   152  	if err != nil {
   153  		return nil, fmt.Errorf("couldn't fetch workflows for %s: %w", ghrepo.FullName(repo), err)
   154  	}
   155  	filtered := []Workflow{}
   156  
   157  	for _, workflow := range workflows {
   158  		desiredState := false
   159  		for _, state := range states {
   160  			if workflow.State == state {
   161  				desiredState = true
   162  				break
   163  			}
   164  		}
   165  
   166  		if !desiredState {
   167  			continue
   168  		}
   169  
   170  		// TODO consider fuzzy or prefix match
   171  		if strings.EqualFold(workflow.Name, name) {
   172  			filtered = append(filtered, workflow)
   173  		}
   174  	}
   175  
   176  	return filtered, nil
   177  }
   178  
   179  func ResolveWorkflow(io *iostreams.IOStreams, client *api.Client, repo ghrepo.Interface, prompt bool, workflowSelector string, states []WorkflowState) (*Workflow, error) {
   180  	if prompt {
   181  		workflows, err := GetWorkflows(client, repo, 0)
   182  		if len(workflows) == 0 {
   183  			err = errors.New("no workflows are enabled")
   184  		}
   185  
   186  		if err != nil {
   187  			var httpErr api.HTTPError
   188  			if errors.As(err, &httpErr) && httpErr.StatusCode == 404 {
   189  				err = errors.New("no workflows are enabled")
   190  			}
   191  
   192  			return nil, fmt.Errorf("could not fetch workflows for %s: %w", ghrepo.FullName(repo), err)
   193  		}
   194  
   195  		return SelectWorkflow(workflows, "Select a workflow", states)
   196  	}
   197  
   198  	workflows, err := FindWorkflow(client, repo, workflowSelector, states)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	if len(workflows) == 0 {
   204  		return nil, fmt.Errorf("could not find any workflows named %s", workflowSelector)
   205  	}
   206  
   207  	if len(workflows) == 1 {
   208  		return &workflows[0], nil
   209  	}
   210  
   211  	if !io.CanPrompt() {
   212  		errMsg := "could not resolve to a unique workflow; found:"
   213  		for _, workflow := range workflows {
   214  			errMsg += fmt.Sprintf(" %s", workflow.Base())
   215  		}
   216  		return nil, errors.New(errMsg)
   217  	}
   218  
   219  	return SelectWorkflow(workflows, "Which workflow do you mean?", states)
   220  }
   221  
   222  func GetWorkflowContent(client *api.Client, repo ghrepo.Interface, workflow Workflow, ref string) ([]byte, error) {
   223  	path := fmt.Sprintf("repos/%s/contents/%s", ghrepo.FullName(repo), workflow.Path)
   224  	if ref != "" {
   225  		q := fmt.Sprintf("?ref=%s", url.QueryEscape(ref))
   226  		path = path + q
   227  	}
   228  
   229  	type Result struct {
   230  		Content string
   231  	}
   232  
   233  	var result Result
   234  	err := client.REST(repo.RepoHost(), "GET", path, nil, &result)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	decoded, err := base64.StdEncoding.DecodeString(result.Content)
   240  	if err != nil {
   241  		return nil, fmt.Errorf("failed to decode workflow file: %w", err)
   242  	}
   243  
   244  	return decoded, nil
   245  }