v0.12.0 dccd128

ci.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package ports
4
5import (
6	"context"
7	"errors"
8	"fmt"
9	"strconv"
10	"time"
11
12	"bnema.dev/ermite/internal/domain"
13)
14
15// CIRunDraft is what CreateRun stores: a run with its source, its jobs and
16// their checks, built once the store has chosen the ids.
17type CIRunDraft struct {
18	Run    domain.CIRun
19	Source domain.CIRunSource
20	Jobs   []domain.CIJob
21	Checks []domain.CICheck
22}
23
24// CIWrite is a set of records written in one atomic batch. A run or a job with
25// Version 0 must not exist yet; any other must be unchanged since it was read.
26// Checks are derived from the jobs and upserted without a precondition.
27// Apply returns domain.ErrConflict otherwise and domain.ErrNotFound when the
28// repository no longer exists.
29//
30// RepoTokens are job repository tokens created with the batch (they must not
31// exist yet), RevokeJobs the jobs whose repository tokens are deleted by it,
32// and Resolutions the action resolutions of a run attempt to keep. They are
33// written under the lock of the run that owns them.
34//
35// Annotations replace, for each (execution of a job, step) they name, all the
36// annotations stored for it (none deletes them); ClearAnnotations deletes all
37// the annotations of the executions of jobs they name, steps included. Both
38// are written with the batch, so a call is applied whole or not at all.
39type CIWrite struct {
40	Runs             []domain.CIRun
41	Jobs             []domain.CIJob
42	Checks           []domain.CICheck
43	RepoTokens       []domain.CIRepoToken
44	RevokeJobs       []CIJobRef
45	Resolutions      []domain.CIActionResolution
46	Annotations      []CIStepAnnotations
47	ClearAnnotations []CILogRef
48}
49
50// CIStepAnnotations are all the annotations of one step of one execution of a
51// job, in the order the step produced them.
52type CIStepAnnotations struct {
53	Repo domain.RepoName
54	Exec CILogRef
55	Step int32
56	// Items are the annotations; none removes those of the step.
57	Items []domain.CIAnnotation
58}
59
60// CIJobRef names one execution of a job.
61type CIJobRef struct {
62	RunID      int64
63	RunAttempt int32
64	JobID      string
65}
66
67// CIRepoTokenStore reads the job repository tokens, for the authentication of
68// a request. They are written by CIStore.Apply. *kvstore.CI implements it.
69type CIRepoTokenStore interface {
70	// FindRepoTokenByHash returns the token whose secret has that hash, or
71	// domain.ErrNotFound.
72	FindRepoTokenByHash(ctx context.Context, hash domain.TokenHash) (domain.CIRepoToken, error)
73	// GetRepoToken returns the token with that id, or domain.ErrNotFound.
74	GetRepoToken(ctx context.Context, id string) (domain.CIRepoToken, error)
75}
76
77// CIStore keeps CI runs, jobs and checks. The store derives its own indexes
78// (the queue of ready jobs, the active jobs, the open runs, the job tokens)
79// from the records it writes.
80type CIStore interface {
81	// CreateRun takes the next run id and the next number of the workflow in
82	// the repository (both never reused), calls build with them and stores
83	// the draft it returns, in one batch that fails with domain.ErrNotFound
84	// once the repository is deleted. The records come back with their
85	// versions.
86	CreateRun(ctx context.Context, repo domain.RepoName, workflowPath string, build func(id, number int64) CIRunDraft) (CIRunDraft, error)
87	// Apply writes the records in one batch and returns the version they all
88	// have afterwards.
89	Apply(ctx context.Context, w CIWrite) (version uint64, err error)
90	// GetRun returns the run, or domain.ErrNotFound.
91	GetRun(ctx context.Context, repo domain.RepoName, id int64) (domain.CIRun, error)
92	// GetActionResolution returns the resolution kept for the key in the run
93	// attempt, or domain.ErrNotFound.
94	GetActionResolution(ctx context.Context, runID int64, runAttempt int32, key string) (domain.CIActionResolution, error)
95	// GetRunSource returns what the run was planned from, or
96	// domain.ErrNotFound. It is how a run is found from its id alone.
97	GetRunSource(ctx context.Context, runID int64) (domain.CIRunSource, error)
98	// ListRuns returns the runs of the repository with an id below before (0
99	// for no bound), newest first, at most limit.
100	ListRuns(ctx context.Context, repo domain.RepoName, before int64, limit int) ([]domain.CIRun, error)
101	// RunIDs returns the ids of the runs of the repository.
102	RunIDs(ctx context.Context, repo domain.RepoName) ([]int64, error)
103	// ListOpenRuns returns the runs that have not completed.
104	ListOpenRuns(ctx context.Context) ([]domain.CIRun, error)
105	// JobAnnotations returns the annotations of one execution of a job
106	// (CIJob.Attempt), ordered by step then by the order the step produced
107	// them. A job without annotation gives an empty list.
108	JobAnnotations(ctx context.Context, runID int64, attempt int32, jobID string) ([]domain.CIAnnotation, error)
109	// GetJob returns a job of a run at an attempt of the run, or
110	// domain.ErrNotFound.
111	GetJob(ctx context.Context, runID int64, runAttempt int32, jobID string) (domain.CIJob, error)
112	// ListJobs returns the jobs of a run at an attempt, ordered by index then
113	// combination.
114	ListJobs(ctx context.Context, runID int64, runAttempt int32) ([]domain.CIJob, error)
115	// FindJobByToken returns the job whose current token has that hash and
116	// whose token is still valid at now, or domain.ErrNotFound.
117	FindJobByToken(ctx context.Context, hash domain.TokenHash, now time.Time) (domain.CIJob, error)
118	// ListReadyJobs returns the jobs that wait for a runner, oldest first, at
119	// most limit.
120	ListReadyJobs(ctx context.Context, limit int) ([]domain.CIJob, error)
121	// ListActiveJobs returns the jobs given to a runner and not over.
122	ListActiveJobs(ctx context.Context) ([]domain.CIJob, error)
123	// ListChecks returns the checks of a commit, at most limit; more is true
124	// when there are more.
125	ListChecks(ctx context.Context, repo domain.RepoName, sha string, limit int) (checks []domain.CICheck, more bool, err error)
126	// Sweep removes the runs completed before the time, with their jobs,
127	// checks and sources (not their numbers), and returns their ids so that
128	// the logs can go too.
129	Sweep(ctx context.Context, before time.Time) ([]int64, error)
130	// CreateRunner stores the runner and the index of its token. A name in use
131	// is domain.ErrAlreadyExists, or with replace the old runner is removed in
132	// the same batch and returned.
133	CreateRunner(ctx context.Context, r domain.CIRunner, replace bool) (removed []domain.CIRunner, err error)
134	// GetRunner returns the runner, or domain.ErrNotFound.
135	GetRunner(ctx context.Context, id string) (domain.CIRunner, error)
136	// FindRunnerByToken returns the runner whose token has that hash, or
137	// domain.ErrNotFound.
138	FindRunnerByToken(ctx context.Context, hash domain.TokenHash) (domain.CIRunner, error)
139	// ListRunners returns every runner ordered by name.
140	ListRunners(ctx context.Context) ([]domain.CIRunner, error)
141	// RemoveRunner removes the runner and its token, or returns
142	// domain.ErrNotFound.
143	RemoveRunner(ctx context.Context, id string) (domain.CIRunner, error)
144}
145
146// CIRunCleaner is what repository deletion needs from CI: the runs of the
147// repository before its records go, and the removal of their logs and
148// summaries after. *usecase.CI implements it.
149type CIRunCleaner interface {
150	RunIDs(ctx context.Context, repo domain.RepoName) ([]int64, error)
151	RemoveLogs(ctx context.Context, runIDs []int64) error
152}
153
154// CIChecksReader is the read side of the checks, for the merge rule and the
155// pages that show them. *usecase.CI implements it.
156type CIChecksReader interface {
157	// CommitChecks returns the checks of a commit, without access control (the
158	// caller has checked it). More than 1,000 is domain.ErrResourceExhausted,
159	// not a part of them: a rule must not decide on some of the checks.
160	CommitChecks(ctx context.Context, repo domain.RepoName, sha string) ([]domain.CICheck, error)
161}
162
163// CILogRef names one execution of a job: its log, its summaries and its
164// annotations are kept under it.
165type CILogRef struct {
166	RunID   int64
167	JobID   string
168	Attempt int32
169}
170
171// ErrCILogLimit is returned by CILogStore.Append when the lines do not fit in
172// the log limit; nothing was appended.
173var ErrCILogLimit = errors.New("log limit reached")
174
175// ErrCILogGap wraps domain.ErrOutOfRange when an offset is above the count.
176type ErrCILogGap struct{ Count int64 }
177
178// Error implements error.
179func (e *ErrCILogGap) Error() string {
180	return "out of range: the log holds lines up to the offset of " + strconv.FormatInt(e.Count, 10)
181}
182
183// Is makes errors.Is(err, domain.ErrOutOfRange) true.
184func (e *ErrCILogGap) Is(target error) bool { return target == domain.ErrOutOfRange }
185
186// CILogStore keeps the logs of the jobs: append-only files, one line per log
187// line, never in the metadata store. Lines are numbered from 0.
188type CILogStore interface {
189	// Append appends the lines at offset (the number of the first line), with
190	// the idempotence of AppendLog: lines already stored are skipped, a gap is
191	// an *ErrCILogGap. It returns the number of lines after the call and how
192	// many of the request were new. A log that would exceed limit bytes gets
193	// nothing and ErrCILogLimit.
194	Append(ctx context.Context, ref CILogRef, offset int64, lines []domain.CILogLine, limit int64) (next int64, added int, err error)
195	// AppendForced appends lines whatever the limit (the last line of a log
196	// that reached it).
197	AppendForced(ctx context.Context, ref CILogRef, lines []domain.CILogLine) (next int64, err error)
198	// Count returns the number of lines held, 0 for a log that does not exist.
199	Count(ctx context.Context, ref CILogRef) (int64, error)
200	// Read returns lines from the one numbered from, at most maxLines lines and
201	// about maxBytes of text. It returns domain.ErrNotFound when the log does
202	// not exist and domain.ErrOutOfRange when from is above the count.
203	Read(ctx context.Context, ref CILogRef, from int64, maxLines, maxBytes int) ([]domain.CILogLine, error)
204	// Changed returns a channel that is closed at the next append to the log.
205	Changed(ref CILogRef) <-chan struct{}
206	// Reset empties the log.
207	Reset(ctx context.Context, ref CILogRef) error
208	// RemoveRuns deletes the logs of the runs.
209	RemoveRuns(ctx context.Context, runIDs []int64) error
210	// RunIDs lists the runs that have a log directory, for the orphan sweep.
211	RunIDs(ctx context.Context) ([]int64, error)
212}
213
214// CISummaryStore keeps the job summaries (the Markdown a step wrote to
215// GITHUB_STEP_SUMMARY): one file per step of an execution of a job, never in
216// the metadata store.
217type CISummaryStore interface {
218	// Put stores the summary of a step, replacing the one it has; the file
219	// appears whole or not at all. An empty markdown removes the summary.
220	Put(ctx context.Context, ref CILogRef, step int32, markdown string) error
221	// List returns the summaries of the steps that have one, by step number,
222	// at most limit; more is true when there are others. With maxBytes above
223	// 0 the files are sized (stat) before they are read, and a summary that
224	// would take the total of the Markdown read past maxBytes is returned
225	// Omitted, unread, as are all the ones after it: a call never reads more
226	// than maxBytes.
227	List(ctx context.Context, ref CILogRef, limit int, maxBytes int64) (summaries []domain.CIStepSummary, more bool, err error)
228	// RemoveJob deletes the summaries of one execution of a job.
229	RemoveJob(ctx context.Context, ref CILogRef) error
230	// RemoveRuns deletes the summaries of the runs.
231	RemoveRuns(ctx context.Context, runIDs []int64) error
232	// RunIDs lists the runs that have a summary directory, for the orphan
233	// sweep.
234	RunIDs(ctx context.Context) ([]int64, error)
235}
236
237// WorkflowTrigger starts the workflows that match pushed refs. OnPush only
238// queues the work and returns: planning happens in the background and its
239// errors never reach the push.
240type WorkflowTrigger interface {
241	OnPush(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate, pusher domain.Principal)
242	// OnMerge is OnPush for the move of a target branch by a merge. GitHub shows
243	// the owner as the actor of the push of a merge, whoever merged; but the
244	// rights the run's actions are resolved with are those of merger, the
245	// principal that merged, never the owner's.
246	OnMerge(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate, merger domain.Principal)
247}
248
249// CIGit is what planning needs from Git beyond RepoReader and MergeGit.
250type CIGit interface {
251	// ChangedPaths returns the paths changed from base to head (a zero base
252	// means the empty tree), in the order of the diff and without renames
253	// detection, at most max; truncated is true when there were more.
254	ChangedPaths(ctx context.Context, repo domain.RepoName, base, head domain.ObjectID, max int) (paths []string, truncated bool, err error)
255	// CommitsOffBranches returns the commits reachable from head and from no
256	// branch (refs/heads/*; tags do not count), newest first, at most limit
257	// (1 to MaxCommitLimit); more is true when there are others. It is empty
258	// for a commit that a branch already has.
259	CommitsOffBranches(ctx context.Context, repo domain.RepoName, head domain.ObjectID, limit int) (commits []domain.Commit, more bool, err error)
260}
261
262// PushObjects tells where the objects of a push in progress are. Git receives a
263// push into a quarantine directory and moves the objects into the repository
264// only when pre-receive accepts it, so the objects a pre-receive check reads are
265// not in the repository yet. Both fields are what git's own environment holds
266// in the hook (GIT_OBJECT_DIRECTORY and GIT_ALTERNATE_OBJECT_DIRECTORIES). The
267// zero value is a push with nothing in quarantine.
268type PushObjects struct {
269	Dir        string
270	Alternates string
271}
272
273// WorkflowGuard finds the changes of a push to the workflow files of a
274// repository.
275type WorkflowGuard interface {
276	// ChangedWorkflow returns the first path under .github/workflows/ that
277	// the updates create or change, from the old tree of each ref (the empty tree
278	// for a new ref) to its new one. A deleted ref changes nothing. "" means
279	// none. objects are those of the push in progress; a directory that is not
280	// inside the repository is refused.
281	ChangedWorkflow(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate, objects PushObjects) (path string, err error)
282}
283
284// --- inbound ---
285
286// RegisterRunnerParams describe a runner to register.
287type RegisterRunnerParams struct {
288	Name      string
289	Labels    []string
290	Version   string
291	Isolation domain.CIIsolation
292	Arch, OS  string
293	Replace   bool
294}
295
296// RegisteredRunner is a new runner with its secret, shown once.
297type RegisteredRunner struct {
298	Runner domain.CIRunner
299	Token  string
300}
301
302// RunnerJob is a job given to a runner.
303type RunnerJob struct {
304	Job   domain.CIJob
305	Token string
306	// GithubToken is the job repository token, the "github.token" of the job.
307	GithubToken  string
308	Repo         domain.RepoName
309	Sha, Ref     string
310	WorkflowPath string
311	WorkflowYAML string
312	// Environment is the environment the job deploys to, "" for none.
313	Environment string
314	// Vars are the merged variables of the job (environment over repository
315	// over instance), already in its "vars" context.
316	Vars map[string]string
317	// Secrets are the secrets the job refers to, in plain text: empty when the
318	// run is not trusted, when the job refers to none and when the server has
319	// no key. They go to the runner and nowhere else: never into the run
320	// record, the github context or a log.
321	Secrets map[string]string
322	// Trusted is the trusted flag of the run: secrets were allowed.
323	Trusted bool
324	// Permissions are the permissions of GithubToken (key to level), after the
325	// trust rule: no write when the run is not trusted.
326	Permissions map[string]string
327}
328
329// String describes the job without its secrets and tokens, so that a job
330// printed by mistake leaks nothing.
331func (j RunnerJob) String() string {
332	return fmt.Sprintf("RunnerJob{job:%s run:%d repo:%s secrets:%d trusted:%t}", j.Job.ID, j.Job.RunID, j.Repo, len(j.Secrets), j.Trusted)
333}
334
335// GoString is String for %#v.
336func (j RunnerJob) GoString() string { return j.String() }
337
338// FetchJobParams selects the job a runner asks for.
339type FetchJobParams struct {
340	Labels []string
341	PollID string
342}
343
344// FetchJobResult is the answer of a poll.
345type FetchJobResult struct {
346	Job *RunnerJob
347	// RetryAfter is how long the runner waits before polling again.
348	RetryAfter time.Duration
349}
350
351// StepUpdate is the state of one step as a runner reports it.
352type StepUpdate struct {
353	Number     int32
354	Name       string
355	State      domain.CIStepState
356	Conclusion domain.CIConclusion
357	// Outcome is how the step ended before continue-on-error; none when the
358	// runner does not report it.
359	Outcome     domain.CIConclusion
360	StartedAt   time.Time
361	CompletedAt time.Time
362}
363
364// CompleteJobParams end a job.
365type CompleteJobParams struct {
366	JobID          string
367	Conclusion     domain.CIConclusion
368	Outputs        map[string]string
369	RunnerShutdown bool
370	Steps          []StepUpdate
371	// Annotations replace those of each step they name, as in UpdateJob.
372	Annotations []domain.CIAnnotation
373}
374
375// CompleteJobResult is the stored result of a job.
376type CompleteJobResult struct {
377	Conclusion domain.CIConclusion
378	Error      string
379	Applied    bool
380}
381
382// ResolvedAction is the resolution of one "uses" of ResolveActions: where to
383// download the action, or Error (then only Ref is set).
384type ResolvedAction struct {
385	Ref           string
386	NameWithOwner string
387	Path          string
388	ResolvedSHA   string
389	TarballURL    string
390	Token         string
391	Source        domain.CIActionSource
392	Error         string
393}
394
395// RemoteRef is a ref of a remote repository, as `git ls-remote` lists it.
396type RemoteRef struct {
397	// Name is the full ref name ("refs/heads/main", "refs/tags/v1").
398	Name string
399	// ID is the object the ref points to (the tag object of an annotated tag).
400	ID string
401	// Peeled is the commit an annotated tag points to; empty for other refs.
402	Peeled string
403}
404
405// GitRemote lists the refs of a repository on github.com, with no
406// credentials. It returns domain.ErrNotFound for a repository that does not
407// exist or needs credentials (GitHub answers alike), and an error matching
408// domain.ErrUnavailable when it cannot tell (network, timeout, too large an
409// answer).
410type GitRemote interface {
411	ListRefs(ctx context.Context, owner, repo string) ([]RemoteRef, error)
412}
413
414// JobSignal is one message of WatchJob.
415type JobSignal struct {
416	Keepalive bool
417	// Cancel is set for a cancel request.
418	Cancel *JobCancel
419	// Ended is set when the job has ended.
420	Ended *domain.JobEndedError
421}
422
423// JobCancel is a cancel request for a running job.
424type JobCancel struct {
425	Reason      domain.CICancelReason
426	Message     string
427	RequestedAt time.Time
428}
429
430// RunnerService is what runners call. The secret is the bearer secret of the
431// call: the runner token for FetchJob, the job token for the other calls.
432// A bad secret is domain.ErrUnauthenticated; a job token of another job than
433// the one named is domain.ErrForbidden. *usecase.CI implements it.
434type RunnerService interface {
435	Register(ctx context.Context, p domain.Principal, params RegisterRunnerParams) (RegisteredRunner, error)
436	FetchJob(ctx context.Context, secret string, params FetchJobParams) (FetchJobResult, error)
437	// UpdateJob applies the steps and the annotations whole or not at all: for
438	// each step number present in annotations, the annotations of that step are
439	// replaced (also for a step already completed, or not in steps).
440	UpdateJob(ctx context.Context, secret, jobID string, steps []StepUpdate, annotations []domain.CIAnnotation) error
441	AppendLog(ctx context.Context, secret, jobID string, step int32, offset int64, lines []domain.CILogLine) (next int64, err error)
442	Heartbeat(ctx context.Context, secret, jobID string) error
443	// WatchJob calls ready once the job token is valid and the stream is
444	// registered, before any message: the adapter flushes the response headers
445	// then, because a client's call returns only when they arrive.
446	WatchJob(ctx context.Context, secret, jobID string, ready func(), send func(JobSignal) error) error
447	CompleteJob(ctx context.Context, secret string, params CompleteJobParams) (CompleteJobResult, error)
448	// ResolveActions resolves the "uses" of the job's steps, one answer per
449	// reference in the same order. A reference that cannot be resolved is not
450	// an error: its answer carries Error. A local ("./") or Docker
451	// ("docker://") reference, or more than 100, is domain.ErrInvalid.
452	ResolveActions(ctx context.Context, secret, jobID string, uses []string) ([]ResolvedAction, error)
453	// UploadSummary stores the summary of a step, replacing the one it has; an
454	// empty markdown removes it. More than 1 MiB, invalid UTF-8 or a step
455	// number outside 1 to 1,000 is domain.ErrInvalid.
456	UploadSummary(ctx context.Context, secret, jobID string, step int32, markdown string) error
457}
458
459// JobView is a job as owners see it.
460type JobView struct {
461	domain.CIJob
462	// Lines is the number of lines of its log now.
463	Lines int64
464	// RunnerGone is set when the runner of the job was removed since.
465	RunnerGone bool
466}
467
468// StepRanges returns where the lines of every step are in the log.
469func (v JobView) StepRanges() []domain.StepRange { return domain.StepRanges(v.Steps, v.Lines) }
470
471// ListRunsParams select a page of runs.
472type ListRunsParams struct {
473	PageSize  int
474	PageToken string
475	// Workflow restricts the result to a workflow file: its path or its file
476	// name. Branch restricts it to the runs of a branch, by short name. Status
477	// restricts it to the runs of that status. Empty means no restriction.
478	Workflow string
479	Branch   string
480	Status   domain.CIStatus
481}
482
483// RunPage is a page of runs, newest first.
484type RunPage struct {
485	Runs          []domain.CIRun
486	NextPageToken string
487}
488
489// RunDetail is a run with its jobs.
490type RunDetail struct {
491	Run           domain.CIRun
492	Jobs          []JobView
493	LatestAttempt int32
494}
495
496// LogBatch is a message of a log stream: consecutive lines from Offset.
497type LogBatch struct {
498	Offset int64
499	Lines  []JobLogLine
500}
501
502// JobLogLine is a log line with its step.
503type JobLogLine struct {
504	Step int32
505	domain.CILogLine
506}
507
508// GetJobLogParams select the log to stream.
509type GetJobLogParams struct {
510	JobID string
511	// Attempt is the attempt of the job, 0 for the latest.
512	Attempt int32
513	Offset  int64
514	Follow  bool
515	// Ready, when not nil, is called once the access is checked and the
516	// stream is counted, before any batch: the adapter flushes the response
517	// headers then (a follow on a job with no line yet would keep the client's
518	// call waiting for the first one).
519	Ready func()
520	// Client identifies where the call comes from, for the limit of streams
521	// per client; empty counts against the total only.
522	Client string
523}
524
525// CheckView is a check with what owners need to read it.
526type CheckView struct {
527	domain.CICheck
528	// WaitingForRunner is set for a queued check whose job no registered
529	// runner can take (no runner has all its labels).
530	WaitingForRunner bool
531}
532
533// ChecksResult are the checks of a commit.
534type ChecksResult struct {
535	Checks    []CheckView
536	Truncated bool
537}
538
539// RunnerPage is a page of runners.
540type RunnerPage struct {
541	Runners       []RunnerView
542	NextPageToken string
543}
544
545// RunnerView is a runner with what is known of it now.
546type RunnerView struct {
547	domain.CIRunner
548	LastSeen time.Time
549	Busy     bool
550}
551
552// JobAnnotation is an annotation with the job it belongs to.
553type JobAnnotation struct {
554	JobID      string
555	Annotation domain.CIAnnotation
556}
557
558// AnnotationList are the annotations of a run, at most
559// domain.CIMaxListedAnnotations.
560type AnnotationList struct {
561	Annotations []JobAnnotation
562	Truncated   bool
563}
564
565// SummaryList are the summaries of a job, at most domain.CIMaxListedSummaries.
566type SummaryList struct {
567	Summaries []domain.CIStepSummary
568	Truncated bool
569}
570
571// RunService is the owner side of CI. *usecase.CI implements it.
572type RunService interface {
573	ListRuns(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListRunsParams) (RunPage, error)
574	GetRun(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32) (RunDetail, error)
575	GetJob(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (JobView, error)
576	GetJobLog(ctx context.Context, p domain.Principal, repo domain.RepoName, params GetJobLogParams, send func(LogBatch) error) error
577	CancelRun(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64) (domain.CIRun, error)
578	RerunRun(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, failedOnly bool) (domain.CIRun, error)
579	ListChecks(ctx context.Context, p domain.Principal, repo domain.RepoName, sha string) (ChecksResult, error)
580	// ListAnnotations returns the annotations of a run attempt (0 for the
581	// latest), of one job when jobID is not empty. Each job gives those of the
582	// attempt in which it executed.
583	ListAnnotations(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32, jobID string) (AnnotationList, error)
584	// GetStepSummaries returns the summaries of a job at an attempt (0 for the
585	// latest), by step number. maxBytes above 0 bounds the Markdown read: the
586	// summaries past it come back Omitted, without their Markdown (the API
587	// passes 0, no bound).
588	GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (SummaryList, error)
589	ListRunners(ctx context.Context, p domain.Principal, size int, token string) (RunnerPage, error)
590	RemoveRunner(ctx context.Context, p domain.Principal, id string) error
591}
592
593// ErrSecretNotOpened means a sealed secret does not open: another key, other
594// additional data (a value moved to another scope or name) or a damaged value.
595var ErrSecretNotOpened = errors.New("secret does not open")
596
597// SecretSealer encrypts the values of secrets at rest. The adapter holds the
598// master key; without one the use cases get no sealer and refuse secrets.
599type SecretSealer interface {
600	// Seal returns plaintext sealed and bound to aad. It never fails.
601	Seal(plaintext, aad []byte) []byte
602	// Open reverses Seal with the same aad, or returns ErrSecretNotOpened.
603	Open(sealed, aad []byte) ([]byte, error)
604	// KeyCheck is a value derived from the key that does not reveal it.
605	KeyCheck() []byte
606}
607
608// SealedSecret is a stored secret: its value is sealed.
609type SealedSecret struct {
610	Name      string
611	Sealed    []byte
612	UpdatedAt time.Time
613}
614
615// CISettingsStore keeps the secrets, variables and environments of CI (ADR
616// 0019). A scope that names a missing repository or environment is
617// domain.ErrNotFound for every write and every read of its content. Names are
618// already upper-cased (domain.ParseSettingName).
619type CISettingsStore interface {
620	// BindSecretsKey records the key check beside the secrets when none is
621	// stored, and returns domain.ErrSecretsKeyMismatch when the stored one
622	// differs.
623	BindSecretsKey(ctx context.Context, check []byte) error
624	// PutSecret creates or replaces a secret.
625	PutSecret(ctx context.Context, scope domain.SettingsScope, name string, sealed []byte, at time.Time) (domain.Secret, error)
626	// GetSealedSecret reads one secret of a scope, or domain.ErrNotFound when
627	// it, the repository or the environment does not exist.
628	GetSealedSecret(ctx context.Context, scope domain.SettingsScope, name string) (SealedSecret, error)
629	// ListSealedSecrets lists the secrets of one scope ordered by name.
630	ListSealedSecrets(ctx context.Context, scope domain.SettingsScope) ([]SealedSecret, error)
631	// DeleteSecret deletes a secret, or returns domain.ErrNotFound.
632	DeleteSecret(ctx context.Context, scope domain.SettingsScope, name string) error
633	// PutVariable creates or replaces a variable.
634	PutVariable(ctx context.Context, scope domain.SettingsScope, name, value string, at time.Time) (domain.Variable, error)
635	// GetVariable returns a variable, or domain.ErrNotFound.
636	GetVariable(ctx context.Context, scope domain.SettingsScope, name string) (domain.Variable, error)
637	// ListVariables lists the variables of one scope ordered by name.
638	ListVariables(ctx context.Context, scope domain.SettingsScope) ([]domain.Variable, error)
639	// DeleteVariable deletes a variable, or returns domain.ErrNotFound.
640	DeleteVariable(ctx context.Context, scope domain.SettingsScope, name string) error
641	// CreateEnvironment creates the environment, or returns the existing one
642	// (compared without regard to case) unchanged.
643	CreateEnvironment(ctx context.Context, repo domain.RepoName, name string, at time.Time) (domain.Environment, error)
644	// ListEnvironments lists the environments of a repository ordered by name.
645	ListEnvironments(ctx context.Context, repo domain.RepoName) ([]domain.Environment, error)
646	// DeleteEnvironment deletes the environment with its secrets and
647	// variables, or returns domain.ErrNotFound.
648	DeleteEnvironment(ctx context.Context, repo domain.RepoName, name string) error
649}
650
651// JobSettingsRequest asks for the variables, and when wanted the secrets, a job
652// sees.
653type JobSettingsRequest struct {
654	Repo domain.RepoName
655	// Environment is the environment of the job, "" for none.
656	Environment string
657	// CreateEnvironment creates the environment first when it does not exist
658	// (a job that names one creates it on first use).
659	CreateEnvironment bool
660	// WantSecrets asks for secrets at all: false for a run that is not
661	// trusted. SecretNames are the (upper-cased) names to return, or AllSecrets
662	// for every secret of the scopes.
663	WantSecrets bool
664	SecretNames []string
665	AllSecrets  bool
666}
667
668// JobSettings are what a job sees: the merged variables and secrets, with the
669// environment over the repository over the instance.
670type JobSettings struct {
671	Vars    map[string]string
672	Secrets map[string]string
673	// SecretsOff is set when secrets were wanted but the server has no
674	// ERMITE_SECRETS_KEY, so none can be given.
675	SecretsOff bool
676}
677
678// CIJobSettings resolves the variables and secrets of a job. *usecase.CISettings
679// implements it.
680type CIJobSettings interface {
681	Resolve(ctx context.Context, req JobSettingsRequest) (JobSettings, error)
682}

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 3

Tags 43

↑↓ moveenter openesc close