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