v0.4.4 edcdd4c

resultssvc.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3// Package resultssvc is the GitHub Actions results service of the runner, the
4// part of it that artifacts v4 use (plan CI-4 C). actions/upload-artifact@v4+
5// and actions/download-artifact@v4+ bundle @actions/artifact v2, which talks
6// Twirp JSON (github.actions.results.api.v1.ArtifactService) to
7// ACTIONS_RESULTS_URL and moves the bytes with Azure block blob requests to the
8// signed URLs that service returns.
9//
10// The runner keeps no artifact: the service translates both protocols to
11// ermite's JobArtifactService, with the job token of the job, and streams the
12// blocks and the downloads through without spooling them. Run and job ids come
13// from the Job the channel serves, never from the bodies of the requests: a
14// job reaches the artifacts of its own run only, and ermite checks that too.
15//
16// A Handler serves the channel of one job, beside the cache (cachesvc): the
17// sandbox reaches both through the same relay. The Twirp calls carry
18// ACTIONS_RUNTIME_TOKEN, which must be a token of that job with the job's
19// Actions.Results scope; the blob URLs carry no bearer (the Azure SDK sends
20// none), so they are signed, bound to the job and expire with its token.
21package resultssvc
22
23import (
24	"crypto/sha256"
25	"encoding/hex"
26	"net/http"
27	"strconv"
28	"strings"
29	"time"
30
31	"bnema.dev/ermite-api/gen/ermite/ermiteconnect"
32	"github.com/bnema/zerowrap"
33)
34
35// Paths the service answers on. The toolkit appends "twirp/github.actions.
36// results.api.v1.ArtifactService/<method>" to ACTIONS_RESULTS_URL
37// (ArtifactHttpClient.request, upload-artifact v4.6.2 dist/upload/index.js:2753);
38// the blob URLs are the service's own, given out in signedUploadUrl and
39// signedUrl.
40const (
41	TwirpPath = "/twirp/github.actions.results.api.v1.ArtifactService/"
42	// BlobPath is followed by "upload/<artifact id>" or "download/<artifact id>".
43	BlobPath = "/_apis/results/blob/"
44)
45
46// Auth is what the service needs from the runner's runtime token signer
47// (cachesvc.Service implements it).
48type Auth interface {
49	// RuntimeToken checks ACTIONS_RUNTIME_TOKEN (a JWT this runner made,
50	// signature and expiry) and returns its job id ("j" claim), its
51	// space-separated scp claim, and its expiry.
52	RuntimeToken(token string) (jobID, scopes string, expiry time.Time, err error)
53	// BlobMAC is an HMAC of the parts under a key domain reserved for blob URL
54	// signatures.
55	BlobMAC(parts ...[]byte) []byte
56}
57
58// Config configures a Service.
59type Config struct {
60	Auth Auth
61	// Now is the clock; nil means time.Now.
62	Now func() time.Time
63	Log zerowrap.Logger
64}
65
66// Service is the results service. It holds no state of its own: one Service
67// serves the channels of every job of the runner.
68type Service struct {
69	auth Auth
70	now  func() time.Time
71	log  zerowrap.Logger
72}
73
74// New returns a Service.
75func New(cfg Config) *Service {
76	if cfg.Now == nil {
77		cfg.Now = time.Now
78	}
79	return &Service{auth: cfg.Auth, now: cfg.Now, log: cfg.Log}
80}
81
82// Job is the job a sandbox channel serves.
83type Job struct {
84	// ID is the Job.job_id.
85	ID string
86	// RunID is the run of the job and Attempt its attempt.
87	RunID   int64
88	Attempt int32
89	// Client calls ermite with the job token (ermitejob_...), which never
90	// reaches the sandbox.
91	Client ermiteconnect.JobArtifactServiceClient
92}
93
94// Handler serves TwirpPath and BlobPath for the channel of one job; any other
95// path is a 404.
96func (s *Service) Handler(job Job) http.Handler {
97	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98		switch {
99		case strings.HasPrefix(r.URL.Path, TwirpPath):
100			s.twirp(job, w, r, strings.TrimPrefix(r.URL.Path, TwirpPath))
101		case strings.HasPrefix(r.URL.Path, BlobPath):
102			s.blob(job, w, r, strings.TrimPrefix(r.URL.Path, BlobPath))
103		default:
104			http.NotFound(w, r)
105		}
106	})
107}
108
109// JobBackendID is the workflow_job_run_backend_id of a job: 32 lowercase hex
110// characters of SHA-256(jobID "/" decimal attempt). It has no ':' since the
111// toolkit splits the scope on ':' (getBackendIdsFromToken, upload-artifact
112// v4.6.2 dist/upload/index.js:3106-3141).
113func JobBackendID(jobID string, attempt int32) string {
114	sum := sha256.Sum256([]byte(jobID + "/" + strconv.FormatInt(int64(attempt), 10)))
115	return hex.EncodeToString(sum[:16])
116}
117
118// Scope is the scp entry of a job: "Actions.Results:<decimal run id>:
119// <JobBackendID>". The toolkit reads its run and job backend ids from it.
120func Scope(runID int64, jobID string, attempt int32) string {
121	return "Actions.Results:" + strconv.FormatInt(runID, 10) + ":" + JobBackendID(jobID, attempt)
122}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close