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 keeps the contents of the CI artifacts and their staged
79// blocks, never in the metadata store. Files are named by ids only.
80type CIArtifactFiles interface {
81 // PutBlock writes src into a new block file of the artifact, synced, and
82 // returns its size. A failure of src or of the write leaves no file.
83 PutBlock(ctx context.Context, ref CIArtifactRef, file string, src io.Reader) (int64, error)
84 // RemoveBlock deletes one block file; a missing one is not an error.
85 RemoveBlock(ctx context.Context, ref CIArtifactRef, file string) error
86 // Assemble concatenates the block files in order into the content of the
87 // artifact, which replaces any earlier one whole, and returns its size and
88 // digest ("sha256:<hex>"). A missing block file is domain.ErrNotFound.
89 Assemble(ctx context.Context, ref CIArtifactRef, files []string) (size int64, digest string, err error)
90 // Open returns the content from offset, or domain.ErrNotFound.
91 Open(ctx context.Context, ref CIArtifactRef, offset int64) (io.ReadCloser, error)
92 // Remove deletes the content and the blocks of the artifact.
93 Remove(ctx context.Context, ref CIArtifactRef) error
94 // RemoveBlocks deletes the blocks of the artifact.
95 RemoveBlocks(ctx context.Context, ref CIArtifactRef) error
96 // RemoveRuns deletes the files of the runs.
97 RemoveRuns(ctx context.Context, runIDs []int64) error
98 // RunIDs lists the runs that have a directory, ArtifactIDs the artifacts
99 // that have files in one, for the orphan sweep.
100 RunIDs(ctx context.Context) ([]int64, error)
101 ArtifactIDs(ctx context.Context, runID int64) ([]int64, error)
102}
103
104// ArtifactLimits are the settings of the artifacts of the server.
105type ArtifactLimits struct {
106 // Retention is the default and longest retention.
107 Retention time.Duration
108 // Quota is the space of the artifacts of one repository, MaxSize the size
109 // of one artifact.
110 Quota int64
111 MaxSize int64
112}
113
114// CreatedArtifact is the answer of CreateJobArtifact.
115type CreatedArtifact struct {
116 ID int64
117 ExpiresAt time.Time
118}
119
120// CreateJobArtifactParams begin an upload.
121type CreateJobArtifactParams struct {
122 JobID string
123 Name string
124 MediaType string
125 // ExpiresAt is the expiry asked for; zero for the default.
126 ExpiresAt time.Time
127}
128
129// ArtifactChunk is one message of a download: the artifact in the first one,
130// then data.
131type ArtifactChunk struct {
132 Artifact *domain.CIArtifact
133 Data []byte
134}
135
136// JobArtifactService is the artifacts side of the runner protocol. The secret
137// is the job token; jobID must be its job (domain.ErrForbidden otherwise), the
138// job must not have ended (a *domain.JobEndedError), and the run is always the
139// job's. *usecase.CI implements it.
140type JobArtifactService interface {
141 CreateJobArtifact(ctx context.Context, secret string, params CreateJobArtifactParams) (CreatedArtifact, error)
142 // UploadJobArtifactBlock stores data as a block of a pending artifact of
143 // the job. An error of data is returned as it is.
144 UploadJobArtifactBlock(ctx context.Context, secret, jobID string, artifactID int64, blockID []byte, data io.Reader) (int64, error)
145 CommitJobArtifact(ctx context.Context, secret, jobID string, artifactID int64, blockIDs [][]byte) (size int64, digest string, err error)
146 FinalizeJobArtifact(ctx context.Context, secret, jobID, name string, size int64, digest string) (domain.CIArtifact, error)
147 ListJobArtifacts(ctx context.Context, secret, jobID, name string, artifactID int64) ([]domain.CIArtifact, error)
148 // DownloadJobArtifact calls send with the artifact, then with the content
149 // from offset in chunks of at most domain.CIArtifactChunk bytes.
150 DownloadJobArtifact(ctx context.Context, secret, jobID string, artifactID, offset int64, send func(ArtifactChunk) error) error
151 DeleteJobArtifact(ctx context.Context, secret, jobID, name string) (int64, error)
152}
153
154// ArtifactList are the artifacts of a run with the space of the repository.
155type ArtifactList struct {
156 Artifacts []domain.CIArtifact
157 Used int64
158 Quota int64
159}
160
161// ArtifactService is the owner side of the artifacts. Listing and downloading
162// need a signed-in caller with repo:read, even on a public repository;
163// deleting needs ci:write. *usecase.CI implements it.
164type ArtifactService interface {
165 ListArtifacts(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64) (ArtifactList, error)
166 // DownloadArtifact is DownloadJobArtifact for an owner.
167 DownloadArtifact(ctx context.Context, p domain.Principal, repo domain.RepoName, artifactID, offset int64, send func(ArtifactChunk) error) error
168 DeleteArtifact(ctx context.Context, p domain.Principal, repo domain.RepoName, artifactID int64) error
169}