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 }