1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package ports
4
5import (
6 "context"
7 "io"
8 "time"
9
10 "bnema.dev/ermite/internal/domain"
11)
12
13// CIArtifactBlock is a staged block of a pending artifact.
14type CIArtifactBlock struct {
15 ArtifactID int64
16 // ID is the uploader's block id (1 to 64 bytes).
17 ID []byte
18 Size int64
19 // File is the name of the block's file in CIArtifactFiles.
20 File string
21}
22
23// CIArtifactWrite is a change of the artifacts of one repository, written in
24// one atomic batch. The batch requires the usage record of the repository at
25// UsageVersion (0: none stored) and stores Used in it: every write of the
26// artifacts of a repository goes through that record, so two writes never both
27// succeed on the same state, and a write fails with domain.ErrConflict when
28// anything of the repository's artifacts changed since UsageVersion was read.
29//
30// Put stores records: one with Version 0 must not exist yet (the batch then
31// also requires the repository, domain.ErrNotFound once it is deleted), any
32// other must be at its Version. Delete removes records at their Version with
33// their staged blocks. PutBlocks stores blocks (replacing one of the same id),
34// DropBlocks removes all the blocks of the artifacts named.
35type CIArtifactWrite struct {
36 Repo domain.RepoName
37 Used int64
38 UsageVersion uint64
39 Put []domain.CIArtifact
40 Delete []domain.CIArtifact
41 PutBlocks []CIArtifactBlock
42 DropBlocks []int64
43}
44
45// CIArtifactStore keeps the records of the CI artifacts, never their contents.
46// *kvstore.CIArtifacts implements it; the records go with their repository.
47type CIArtifactStore interface {
48 // NextArtifactID takes the next artifact id: global, increasing, never
49 // reused.
50 NextArtifactID(ctx context.Context) (int64, error)
51 // ArtifactUsage returns the bytes charged to the artifacts of the
52 // repository and the version of the record (0, 0 when there is none).
53 ArtifactUsage(ctx context.Context, repo domain.RepoName) (used int64, version uint64, err error)
54 // GetArtifact returns an artifact by id, or domain.ErrNotFound.
55 GetArtifact(ctx context.Context, id int64) (domain.CIArtifact, error)
56 // RunArtifacts returns the artifacts of a run in every state, by id.
57 RunArtifacts(ctx context.Context, repo domain.RepoName, runID int64) ([]domain.CIArtifact, error)
58 // AllArtifacts returns every artifact, for the sweep.
59 AllArtifacts(ctx context.Context) ([]domain.CIArtifact, error)
60 // ArtifactBlocks returns the staged blocks of an artifact.
61 ArtifactBlocks(ctx context.Context, id int64) ([]CIArtifactBlock, error)
62 // BlockArtifactIDs returns the artifacts that have staged blocks, for the
63 // sweep of blocks left by a deleted artifact.
64 BlockArtifactIDs(ctx context.Context) ([]int64, error)
65 // ApplyArtifacts writes w in one batch.
66 ApplyArtifacts(ctx context.Context, w CIArtifactWrite) error
67 // DeleteBlocks removes the staged blocks of an artifact that no longer
68 // exists (left by a repository deleted during an upload).
69 DeleteBlocks(ctx context.Context, id int64) error
70}
71
72// CIArtifactRef names the files of one artifact.
73type CIArtifactRef struct {
74 RunID int64
75 ID int64
76}
77
78// CIArtifactFiles reads and removes the artifacts recorded before the blob
79// store (ADR 0034); removed once they have all expired. *ciartifacts.Store
80// implements it. Files are named by ids only.
81type CIArtifactFiles interface {
82 // Open returns the content from offset, or domain.ErrNotFound.
83 Open(ctx context.Context, ref CIArtifactRef, offset int64) (io.ReadCloser, error)
84 // Remove deletes the content and the blocks of the artifact.
85 Remove(ctx context.Context, ref CIArtifactRef) error
86 // RemoveRuns deletes the files of the runs.
87 RemoveRuns(ctx context.Context, runIDs []int64) error
88 // RunIDs lists the runs that have a directory, ArtifactIDs the artifacts
89 // that have files in one, for the orphan sweep.
90 RunIDs(ctx context.Context) ([]int64, error)
91 ArtifactIDs(ctx context.Context, runID int64) ([]int64, error)
92}
93
94// ArtifactLimits are the settings of the artifacts of the server.
95type ArtifactLimits struct {
96 // Retention is the default and longest retention.
97 Retention time.Duration
98 // Quota is the space of the artifacts of one repository, MaxSize the size
99 // of one artifact.
100 Quota int64
101 MaxSize int64
102}
103
104// CreatedArtifact is the answer of CreateJobArtifact.
105type CreatedArtifact struct {
106 ID int64
107 ExpiresAt time.Time
108}
109
110// CreateJobArtifactParams begin an upload.
111type CreateJobArtifactParams struct {
112 JobID string
113 Name string
114 MediaType string
115 // ExpiresAt is the expiry asked for; zero for the default.
116 ExpiresAt time.Time
117}
118
119// ArtifactChunk is one message of a download: the artifact in the first one,
120// then data.
121type ArtifactChunk struct {
122 Artifact *domain.CIArtifact
123 Data []byte
124}
125
126// JobArtifactService is the artifacts side of the runner protocol. The secret
127// is the job token; jobID must be its job (domain.ErrForbidden otherwise), the
128// job must not have ended (a *domain.JobEndedError), and the run is always the
129// job's. *usecase.CI implements it.
130type JobArtifactService interface {
131 CreateJobArtifact(ctx context.Context, secret string, params CreateJobArtifactParams) (CreatedArtifact, error)
132 // UploadJobArtifactBlock stores data as a block of a pending artifact of
133 // the job. An error of data is returned as it is.
134 UploadJobArtifactBlock(ctx context.Context, secret, jobID string, artifactID int64, blockID []byte, data io.Reader) (int64, error)
135 CommitJobArtifact(ctx context.Context, secret, jobID string, artifactID int64, blockIDs [][]byte) (size int64, digest string, err error)
136 FinalizeJobArtifact(ctx context.Context, secret, jobID, name string, size int64, digest string) (domain.CIArtifact, error)
137 ListJobArtifacts(ctx context.Context, secret, jobID, name string, artifactID int64) ([]domain.CIArtifact, error)
138 // DownloadJobArtifact calls send with the artifact, then with the content
139 // from offset in chunks of at most domain.CIArtifactChunk bytes.
140 DownloadJobArtifact(ctx context.Context, secret, jobID string, artifactID, offset int64, send func(ArtifactChunk) error) error
141 DeleteJobArtifact(ctx context.Context, secret, jobID, name string) (int64, error)
142}
143
144// ArtifactList are the artifacts of a run with the space of the repository.
145type ArtifactList struct {
146 Artifacts []domain.CIArtifact
147 Used int64
148 Quota int64
149}
150
151// ArtifactService is the owner side of the artifacts. Listing and downloading
152// need a signed-in caller with repo:read, even on a public repository;
153// deleting needs ci:write. *usecase.CI implements it.
154type ArtifactService interface {
155 ListArtifacts(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64) (ArtifactList, error)
156 // DownloadArtifact is DownloadJobArtifact for an owner.
157 DownloadArtifact(ctx context.Context, p domain.Principal, repo domain.RepoName, artifactID, offset int64, send func(ArtifactChunk) error) error
158 DeleteArtifact(ctx context.Context, p domain.Principal, repo domain.RepoName, artifactID int64) error
159}