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// --- inbound ---
263
264// RegisterRunnerParams describe a runner to register.
265type RegisterRunnerParams struct {
266 Name string
267 Labels []string
268 Version string
269 Isolation domain.CIIsolation
270 Arch, OS string
271 Replace bool
272}
273
274// RegisteredRunner is a new runner with its secret, shown once.
275type RegisteredRunner struct {
276 Runner domain.CIRunner
277 Token string
278}
279
280// RunnerJob is a job given to a runner.
281type RunnerJob struct {
282 Job domain.CIJob
283 Token string
284 // GithubToken is the job repository token, the "github.token" of the job.
285 GithubToken string
286 Repo domain.RepoName
287 Sha, Ref string
288 WorkflowPath string
289 WorkflowYAML string
290 // Environment is the environment the job deploys to, "" for none.
291 Environment string
292 // Vars are the merged variables of the job (environment over repository
293 // over instance), already in its "vars" context.
294 Vars map[string]string
295 // Secrets are the secrets the job refers to, in plain text: empty when the
296 // run is not trusted, when the job refers to none and when the server has
297 // no key. They go to the runner and nowhere else: never into the run
298 // record, the github context or a log.
299 Secrets map[string]string
300 // Trusted is the trusted flag of the run: secrets were allowed.
301 Trusted bool
302}
303
304// String describes the job without its secrets and tokens, so that a job
305// printed by mistake leaks nothing.
306func (j RunnerJob) String() string {
307 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)
308}
309
310// GoString is String for %#v.
311func (j RunnerJob) GoString() string { return j.String() }
312
313// FetchJobParams selects the job a runner asks for.
314type FetchJobParams struct {
315 Labels []string
316 PollID string
317}
318
319// FetchJobResult is the answer of a poll.
320type FetchJobResult struct {
321 Job *RunnerJob
322 // RetryAfter is how long the runner waits before polling again.
323 RetryAfter time.Duration
324}
325
326// StepUpdate is the state of one step as a runner reports it.
327type StepUpdate struct {
328 Number int32
329 Name string
330 State domain.CIStepState
331 Conclusion domain.CIConclusion
332 // Outcome is how the step ended before continue-on-error; none when the
333 // runner does not report it.
334 Outcome domain.CIConclusion
335 StartedAt time.Time
336 CompletedAt time.Time
337}
338
339// CompleteJobParams end a job.
340type CompleteJobParams struct {
341 JobID string
342 Conclusion domain.CIConclusion
343 Outputs map[string]string
344 RunnerShutdown bool
345 Steps []StepUpdate
346 // Annotations replace those of each step they name, as in UpdateJob.
347 Annotations []domain.CIAnnotation
348}
349
350// CompleteJobResult is the stored result of a job.
351type CompleteJobResult struct {
352 Conclusion domain.CIConclusion
353 Error string
354 Applied bool
355}
356
357// ResolvedAction is the resolution of one "uses" of ResolveActions: where to
358// download the action, or Error (then only Ref is set).
359type ResolvedAction struct {
360 Ref string
361 NameWithOwner string
362 Path string
363 ResolvedSHA string
364 TarballURL string
365 Token string
366 Source domain.CIActionSource
367 Error string
368}
369
370// RemoteRef is a ref of a remote repository, as `git ls-remote` lists it.
371type RemoteRef struct {
372 // Name is the full ref name ("refs/heads/main", "refs/tags/v1").
373 Name string
374 // ID is the object the ref points to (the tag object of an annotated tag).
375 ID string
376 // Peeled is the commit an annotated tag points to; empty for other refs.
377 Peeled string
378}
379
380// GitRemote lists the refs of a repository on github.com, with no
381// credentials. It returns domain.ErrNotFound for a repository that does not
382// exist or needs credentials (GitHub answers alike), and an error matching
383// domain.ErrUnavailable when it cannot tell (network, timeout, too large an
384// answer).
385type GitRemote interface {
386 ListRefs(ctx context.Context, owner, repo string) ([]RemoteRef, error)
387}
388
389// JobSignal is one message of WatchJob.
390type JobSignal struct {
391 Keepalive bool
392 // Cancel is set for a cancel request.
393 Cancel *JobCancel
394 // Ended is set when the job has ended.
395 Ended *domain.JobEndedError
396}
397
398// JobCancel is a cancel request for a running job.
399type JobCancel struct {
400 Reason domain.CICancelReason
401 Message string
402 RequestedAt time.Time
403}
404
405// RunnerService is what runners call. The secret is the bearer secret of the
406// call: the runner token for FetchJob, the job token for the other calls.
407// A bad secret is domain.ErrUnauthenticated; a job token of another job than
408// the one named is domain.ErrForbidden. *usecase.CI implements it.
409type RunnerService interface {
410 Register(ctx context.Context, p domain.Principal, params RegisterRunnerParams) (RegisteredRunner, error)
411 FetchJob(ctx context.Context, secret string, params FetchJobParams) (FetchJobResult, error)
412 // UpdateJob applies the steps and the annotations whole or not at all: for
413 // each step number present in annotations, the annotations of that step are
414 // replaced (also for a step already completed, or not in steps).
415 UpdateJob(ctx context.Context, secret, jobID string, steps []StepUpdate, annotations []domain.CIAnnotation) error
416 AppendLog(ctx context.Context, secret, jobID string, step int32, offset int64, lines []domain.CILogLine) (next int64, err error)
417 Heartbeat(ctx context.Context, secret, jobID string) error
418 // WatchJob calls ready once the job token is valid and the stream is
419 // registered, before any message: the adapter flushes the response headers
420 // then, because a client's call returns only when they arrive.
421 WatchJob(ctx context.Context, secret, jobID string, ready func(), send func(JobSignal) error) error
422 CompleteJob(ctx context.Context, secret string, params CompleteJobParams) (CompleteJobResult, error)
423 // ResolveActions resolves the "uses" of the job's steps, one answer per
424 // reference in the same order. A reference that cannot be resolved is not
425 // an error: its answer carries Error. A local ("./") or Docker
426 // ("docker://") reference, or more than 100, is domain.ErrInvalid.
427 ResolveActions(ctx context.Context, secret, jobID string, uses []string) ([]ResolvedAction, error)
428 // UploadSummary stores the summary of a step, replacing the one it has; an
429 // empty markdown removes it. More than 1 MiB, invalid UTF-8 or a step
430 // number outside 1 to 1,000 is domain.ErrInvalid.
431 UploadSummary(ctx context.Context, secret, jobID string, step int32, markdown string) error
432}
433
434// JobView is a job as owners see it.
435type JobView struct {
436 domain.CIJob
437 // Lines is the number of lines of its log now.
438 Lines int64
439 // RunnerGone is set when the runner of the job was removed since.
440 RunnerGone bool
441}
442
443// StepRanges returns where the lines of every step are in the log.
444func (v JobView) StepRanges() []domain.StepRange { return domain.StepRanges(v.Steps, v.Lines) }
445
446// ListRunsParams select a page of runs.
447type ListRunsParams struct {
448 PageSize int
449 PageToken string
450 // Workflow restricts the result to a workflow file: its path or its file
451 // name. Branch restricts it to the runs of a branch, by short name. Status
452 // restricts it to the runs of that status. Empty means no restriction.
453 Workflow string
454 Branch string
455 Status domain.CIStatus
456}
457
458// RunPage is a page of runs, newest first.
459type RunPage struct {
460 Runs []domain.CIRun
461 NextPageToken string
462}
463
464// RunDetail is a run with its jobs.
465type RunDetail struct {
466 Run domain.CIRun
467 Jobs []JobView
468 LatestAttempt int32
469}
470
471// LogBatch is a message of a log stream: consecutive lines from Offset.
472type LogBatch struct {
473 Offset int64
474 Lines []JobLogLine
475}
476
477// JobLogLine is a log line with its step.
478type JobLogLine struct {
479 Step int32
480 domain.CILogLine
481}
482
483// GetJobLogParams select the log to stream.
484type GetJobLogParams struct {
485 JobID string
486 // Attempt is the attempt of the job, 0 for the latest.
487 Attempt int32
488 Offset int64
489 Follow bool
490 // Ready, when not nil, is called once the access is checked and the
491 // stream is counted, before any batch: the adapter flushes the response
492 // headers then (a follow on a job with no line yet would keep the client's
493 // call waiting for the first one).
494 Ready func()
495 // Client identifies where the call comes from, for the limit of streams
496 // per client; empty counts against the total only.
497 Client string
498}
499
500// CheckView is a check with what owners need to read it.
501type CheckView struct {
502 domain.CICheck
503 // WaitingForRunner is set for a queued check whose job no registered
504 // runner can take (no runner has all its labels).
505 WaitingForRunner bool
506}
507
508// ChecksResult are the checks of a commit.
509type ChecksResult struct {
510 Checks []CheckView
511 Truncated bool
512}
513
514// RunnerPage is a page of runners.
515type RunnerPage struct {
516 Runners []RunnerView
517 NextPageToken string
518}
519
520// RunnerView is a runner with what is known of it now.
521type RunnerView struct {
522 domain.CIRunner
523 LastSeen time.Time
524 Busy bool
525}
526
527// JobAnnotation is an annotation with the job it belongs to.
528type JobAnnotation struct {
529 JobID string
530 Annotation domain.CIAnnotation
531}
532
533// AnnotationList are the annotations of a run, at most
534// domain.CIMaxListedAnnotations.
535type AnnotationList struct {
536 Annotations []JobAnnotation
537 Truncated bool
538}
539
540// SummaryList are the summaries of a job, at most domain.CIMaxListedSummaries.
541type SummaryList struct {
542 Summaries []domain.CIStepSummary
543 Truncated bool
544}
545
546// RunService is the owner side of CI. *usecase.CI implements it.
547type RunService interface {
548 ListRuns(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListRunsParams) (RunPage, error)
549 GetRun(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32) (RunDetail, error)
550 GetJob(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (JobView, error)
551 GetJobLog(ctx context.Context, p domain.Principal, repo domain.RepoName, params GetJobLogParams, send func(LogBatch) error) error
552 CancelRun(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64) (domain.CIRun, error)
553 RerunRun(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, failedOnly bool) (domain.CIRun, error)
554 ListChecks(ctx context.Context, p domain.Principal, repo domain.RepoName, sha string) (ChecksResult, error)
555 // ListAnnotations returns the annotations of a run attempt (0 for the
556 // latest), of one job when jobID is not empty. Each job gives those of the
557 // attempt in which it executed.
558 ListAnnotations(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32, jobID string) (AnnotationList, error)
559 // GetStepSummaries returns the summaries of a job at an attempt (0 for the
560 // latest), by step number. maxBytes above 0 bounds the Markdown read: the
561 // summaries past it come back Omitted, without their Markdown (the API
562 // passes 0, no bound).
563 GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (SummaryList, error)
564 ListRunners(ctx context.Context, p domain.Principal, size int, token string) (RunnerPage, error)
565 RemoveRunner(ctx context.Context, p domain.Principal, id string) error
566}
567
568// ErrSecretNotOpened means a sealed secret does not open: another key, other
569// additional data (a value moved to another scope or name) or a damaged value.
570var ErrSecretNotOpened = errors.New("secret does not open")
571
572// SecretSealer encrypts the values of secrets at rest. The adapter holds the
573// master key; without one the use cases get no sealer and refuse secrets.
574type SecretSealer interface {
575 // Seal returns plaintext sealed and bound to aad. It never fails.
576 Seal(plaintext, aad []byte) []byte
577 // Open reverses Seal with the same aad, or returns ErrSecretNotOpened.
578 Open(sealed, aad []byte) ([]byte, error)
579 // KeyCheck is a value derived from the key that does not reveal it.
580 KeyCheck() []byte
581}
582
583// SealedSecret is a stored secret: its value is sealed.
584type SealedSecret struct {
585 Name string
586 Sealed []byte
587 UpdatedAt time.Time
588}
589
590// CISettingsStore keeps the secrets, variables and environments of CI (ADR
591// 0019). A scope that names a missing repository or environment is
592// domain.ErrNotFound for every write and every read of its content. Names are
593// already upper-cased (domain.ParseSettingName).
594type CISettingsStore interface {
595 // BindSecretsKey records the key check beside the secrets when none is
596 // stored, and returns domain.ErrSecretsKeyMismatch when the stored one
597 // differs.
598 BindSecretsKey(ctx context.Context, check []byte) error
599 // PutSecret creates or replaces a secret.
600 PutSecret(ctx context.Context, scope domain.SettingsScope, name string, sealed []byte, at time.Time) (domain.Secret, error)
601 // GetSealedSecret reads one secret of a scope, or domain.ErrNotFound when
602 // it, the repository or the environment does not exist.
603 GetSealedSecret(ctx context.Context, scope domain.SettingsScope, name string) (SealedSecret, error)
604 // ListSealedSecrets lists the secrets of one scope ordered by name.
605 ListSealedSecrets(ctx context.Context, scope domain.SettingsScope) ([]SealedSecret, error)
606 // DeleteSecret deletes a secret, or returns domain.ErrNotFound.
607 DeleteSecret(ctx context.Context, scope domain.SettingsScope, name string) error
608 // PutVariable creates or replaces a variable.
609 PutVariable(ctx context.Context, scope domain.SettingsScope, name, value string, at time.Time) (domain.Variable, error)
610 // GetVariable returns a variable, or domain.ErrNotFound.
611 GetVariable(ctx context.Context, scope domain.SettingsScope, name string) (domain.Variable, error)
612 // ListVariables lists the variables of one scope ordered by name.
613 ListVariables(ctx context.Context, scope domain.SettingsScope) ([]domain.Variable, error)
614 // DeleteVariable deletes a variable, or returns domain.ErrNotFound.
615 DeleteVariable(ctx context.Context, scope domain.SettingsScope, name string) error
616 // CreateEnvironment creates the environment, or returns the existing one
617 // (compared without regard to case) unchanged.
618 CreateEnvironment(ctx context.Context, repo domain.RepoName, name string, at time.Time) (domain.Environment, error)
619 // ListEnvironments lists the environments of a repository ordered by name.
620 ListEnvironments(ctx context.Context, repo domain.RepoName) ([]domain.Environment, error)
621 // DeleteEnvironment deletes the environment with its secrets and
622 // variables, or returns domain.ErrNotFound.
623 DeleteEnvironment(ctx context.Context, repo domain.RepoName, name string) error
624}
625
626// JobSettingsRequest asks for the variables, and when wanted the secrets, a job
627// sees.
628type JobSettingsRequest struct {
629 Repo domain.RepoName
630 // Environment is the environment of the job, "" for none.
631 Environment string
632 // CreateEnvironment creates the environment first when it does not exist
633 // (a job that names one creates it on first use).
634 CreateEnvironment bool
635 // WantSecrets asks for secrets at all: false for a run that is not
636 // trusted. SecretNames are the (upper-cased) names to return, or AllSecrets
637 // for every secret of the scopes.
638 WantSecrets bool
639 SecretNames []string
640 AllSecrets bool
641}
642
643// JobSettings are what a job sees: the merged variables and secrets, with the
644// environment over the repository over the instance.
645type JobSettings struct {
646 Vars map[string]string
647 Secrets map[string]string
648 // SecretsOff is set when secrets were wanted but the server has no
649 // ERMITE_SECRETS_KEY, so none can be given.
650 SecretsOff bool
651}
652
653// CIJobSettings resolves the variables and secrets of a job. *usecase.CISettings
654// implements it.
655type CIJobSettings interface {
656 Resolve(ctx context.Context, req JobSettingsRequest) (JobSettings, error)
657}