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