go.temporal.io/server/tools/common/github/runs.go

269 LOC · 5 covered · 264 uncovered · 3 ranges · 3 concepts · 3 introducers · 3 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 package github
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strconv"
8 "strings"
9 "time"
10 )
11
12 // Conclusion represents the conclusion status of a workflow run or job.
13 type Conclusion string
14
15 const (
16 ConclusionSuccess Conclusion = "success"
17 ConclusionFailure Conclusion = "failure"
18 )
19
20 // Run represents a GitHub Actions workflow run returned by the gh CLI.
21 type Run struct {
22 DatabaseID int64 `json:"databaseId"`
23 Number int `json:"number"`
24 Name string `json:"name"`
25 WorkflowName string `json:"workflowName"`
26 WorkflowDatabaseID int64 `json:"workflowDatabaseId"`
27 Status string `json:"status"`
28 Conclusion Conclusion `json:"conclusion"`
29 HeadBranch string `json:"headBranch"`
30 HeadSHA string `json:"headSha"`
31 URL string `json:"url"`
32 DisplayTitle string `json:"displayTitle"`
33 Event string `json:"event"`
34 CreatedAt time.Time `json:"createdAt"`
35 StartedAt time.Time `json:"startedAt"`
36 UpdatedAt time.Time `json:"updatedAt"`
37 Duration time.Duration `json:"-"`
38 Jobs []Job `json:"jobs"`
39 }
40
41 // ShortSHA returns the first seven characters of the run's commit SHA.
42 > func (r Run) ShortSHA() string { runs.go ×1
43 > if len(r.HeadSHA) > 7 {
44 > return r.HeadSHA[:7] runs.go ×1
45 > }
46 > return r.HeadSHA runs.go ×1
47 }
48
49 type workflowRunResponse struct {
50 ID int64 `json:"id"`
51 Number int `json:"run_number"`
52 Name string `json:"name"`
53 Status string `json:"status"`
54 Conclusion Conclusion `json:"conclusion"`
55 HeadBranch string `json:"head_branch"`
56 HeadSHA string `json:"head_sha"`
57 URL string `json:"html_url"`
58 Event string `json:"event"`
59 CreatedAt time.Time `json:"created_at"`
60 UpdatedAt time.Time `json:"updated_at"`
61 WorkflowID int64 `json:"workflow_id"`
62 }
63
64 // Job represents a single job in a GitHub Actions workflow run returned by the gh CLI.
65 type Job struct {
66 Name string `json:"name"`
67 Conclusion Conclusion `json:"conclusion"`
68 Status string `json:"status"`
69 StartedAt string `json:"startedAt"`
70 CompletedAt string `json:"completedAt"`
71 URL string `json:"url"`
72 }
73
74 // ViewRun retrieves workflow run details through the gh CLI.
75 func ViewRun(ctx context.Context, runID string) (Run, error) {
76 fields := []string{
77 "databaseId",
78 "number",
79 "conclusion",
80 "name",
81 "workflowName",
82 "workflowDatabaseId",
83 "status",
84 "headBranch",
85 "headSha",
86 "url",
87 "displayTitle",
88 "event",
89 "createdAt",
90 "startedAt",
91 "updatedAt",
92 "jobs",
93 }
94
95 var run Run
96 if err := runJSON(ctx, []string{"view", runID}, fields, &run); err != nil {
97 return Run{}, fmt.Errorf("failed to get workflow run: %w", err)
98 }
99 return run, nil
100 }
101
102 func runJSON(ctx context.Context, args []string, fields []string, out any) error {
103 cmdArgs := append([]string{"run"}, args...)
104 if len(fields) > 0 {
105 cmdArgs = append(cmdArgs, "--json", strings.Join(fields, ","))
106 }
107
108 output, err := commandOutput(ctx, defaultTimeout, cmdArgs...)
109 if err != nil {
110 return err
111 }
112 if err := json.Unmarshal(output, out); err != nil {
113 return fmt.Errorf("failed to parse gh run response: %w", err)
114 }
115 return nil
116 }
117
118 // RunListOptions configures workflow-run listing.
119 type RunListOptions struct {
120 Repo string
121 WorkflowID int64
122 Branch string
123 Workflow string
124 Event string
125 Status string
126 Created string
127 Limit int
128 All bool
129 }
130
131 // ListRuns retrieves workflow runs through the gh CLI.
132 func ListRuns(ctx context.Context, opts RunListOptions) ([]Run, error) {
133 if opts.WorkflowID != 0 {
134 return listWorkflowRuns(ctx, opts)
135 }
136
137 fields := []string{
138 "databaseId",
139 "number",
140 "conclusion",
141 "name",
142 "workflowName",
143 "workflowDatabaseId",
144 "status",
145 "event",
146 "createdAt",
147 "startedAt",
148 "updatedAt",
149 "headSha",
150 "displayTitle",
151 "url",
152 }
153
154 var runs []Run
155 if err := runJSON(ctx, append([]string{"list"}, runListArgs(opts)...), fields, &runs); err != nil {
156 return nil, fmt.Errorf("failed to list workflow runs: %w", err)
157 }
158 return runs, nil
159 }
160
161 func listWorkflowRuns(ctx context.Context, opts RunListOptions) ([]Run, error) {
162 var allRuns []Run
163
164 page := 1
165 for {
166 var response struct {
167 WorkflowRuns []workflowRunResponse `json:"workflow_runs"`
168 }
169 path := fmt.Sprintf(
170 "/repos/%s/actions/workflows/%d/runs?branch=%s&created=%s&per_page=100&page=%d",
171 opts.Repo,
172 opts.WorkflowID,
173 opts.Branch,
174 opts.Created,
175 page,
176 )
177 if err := getJSON(ctx, path, &response); err != nil {
178 return nil, fmt.Errorf("failed to fetch workflow runs page %d: %w", page, err)
179 }
180
181 if len(response.WorkflowRuns) == 0 {
182 break
183 }
184
185 for _, run := range response.WorkflowRuns {
186 allRuns = append(allRuns, run.toRun())
187 }
188 if opts.Limit > 0 && len(allRuns) >= opts.Limit {
189 return allRuns[:opts.Limit], nil
190 }
191 if len(response.WorkflowRuns) < 100 {
192 break
193 }
194
195 page++
196 }
197
198 return allRuns, nil
199 }
200
201 func (r workflowRunResponse) toRun() Run {
202 return Run{
203 DatabaseID: r.ID,
204 Number: r.Number,
205 Name: r.Name,
206 WorkflowName: r.Name,
207 WorkflowDatabaseID: r.WorkflowID,
208 Status: r.Status,
209 Conclusion: r.Conclusion,
210 HeadBranch: r.HeadBranch,
211 HeadSHA: r.HeadSHA,
212 URL: r.URL,
213 Event: r.Event,
214 CreatedAt: r.CreatedAt,
215 UpdatedAt: r.UpdatedAt,
216 }
217 }
218
219 func runListArgs(opts RunListOptions) []string {
220 var args []string
221 if opts.Repo != "" {
222 args = append(args, "--repo", opts.Repo)
223 }
224 if opts.Branch != "" {
225 args = append(args, "--branch", opts.Branch)
226 }
227 if opts.Workflow != "" {
228 args = append(args, "--workflow", opts.Workflow)
229 }
230 if opts.Event != "" {
231 args = append(args, "--event", opts.Event)
232 }
233 if opts.Status != "" {
234 args = append(args, "--status", opts.Status)
235 }
236 if opts.Created != "" {
237 args = append(args, "--created", opts.Created)
238 }
239 if opts.Limit > 0 {
240 args = append(args, "--limit", strconv.Itoa(opts.Limit))
241 }
242 if opts.All {
243 args = append(args, "--all")
244 }
245 return args
246 }
247
248 // RunDownloadOptions configures `gh run download`.
249 type RunDownloadOptions struct {
250 Repo string
251 Pattern string
252 Dir string
253 }
254
255 // RunDownload executes `gh run download`.
256 func RunDownload(ctx context.Context, runID string, opts RunDownloadOptions) error {
257 args := []string{"run", "download", runID}
258 if opts.Repo != "" {
259 args = append(args, "--repo", opts.Repo)
260 }
261 if opts.Pattern != "" {
262 args = append(args, "--pattern", opts.Pattern)
263 }
264 if opts.Dir != "" {
265 args = append(args, "--dir", opts.Dir)
266 }
267
268 return commandRun(ctx, defaultTimeout, args...)
269 }